Barely a week passes without somebody posting on the NTDEV list about power state transition failures related to WDF Queues. “I can’t stop my Queue” or “When I attempt to set my system into Standby, I sporadically get a bugcheck 0x9F”, are the most common issues I hear. I also hear about people getting SDV errors for drivers that passed in Win7 and have been otherwise working for ages. Or, I get an email from a former student asking about the (excessively long yet surprisingly) cryptic comment and its associated _analysis_assume shown in Figure 1, which appears close to twenty times in the driver examples in recent WDKs.
D-State Changes with Requests in Progress
All these issues are related to the simple fact that when the system wants to transition a device out of D0 (Working) State to a lower-power D-State, any “active” I/O requests on that device must be “accounted for” by the driver.
The fact that outstanding Requests need to be considered when your device is powering off is a pretty basic concept. When your device is being powered-off, if you have read or write Requests in progress you’re going to have to do something with those Requests. You need to abort them (returning an error to the user), stop them and restart them again when you device powers back up, or… something. You need to do this because one thing you can count on is the fact that your device won’t be completing that Request while it’s powered off.
Back in the caveman days of WDM, it was entirely up to you to figure this out and manage the process. Drivers “losing” I/O operations across power state transitions wasn’t exactly unheard of. Fortunately, we no longer have to deal with WDM any more than we have to find and club our own food. The WDF Framework once again has come to rescue us from the Stone Age.
So, what happens in WDF? In WDF by default any WDF Queues that you create are “power managed.” When a WDF Queue is power managed, it means that the state of the Queue follows the power state of its associated device. Specifically, whenever the device is powered-on (in D0 State) the Queue will present Requests to the driver based on the Queue’s Dispatch Type. For convenience, we’ll say the Queue is in the Started state. When the device attempts to transition from powered on (D0) to a lower powered state (Dx), all power managed Queues for the device will no longer present Requests to the driver. Once again, for convenience, we’ll refer to this as the Queue being in the Stopped state.
When the system wants to transition your device from D0 to Dx, it tells the WDF Framework. The Framework (does a bunch of stuff and then) attempts to transition each of your device’s power managed Queues from Started to Stopped state. And this is where the misunderstanding frequently begins. One of the key points people often miss is that the Framework will delay the device’s D0 to Dx power state transition until all of the device’s power managed Queues have successfully entered the Stopped state. If that delay lasts too long, the system bugchecks with a 0x9F crash code.
Stopping WDF Queues
Simple, right? Your device needs to go to Dx, but before it can do so, the Framework moves all its power managed Queues to the Stopped state. So, you may ask, what needs to happen before a Queue can enter Stopped?
Before WDF can successfully transition a power managed Queue to Stopped, all Requests that have been presented to your driver from that Queue and are still active need to be accounted for. This is how WDF helps you ensure that you don’t lose any I/O requests across a power-state transition. The Framework considers a Request to be “active” until your driver has done one of the following things with it:
- Completed it by calling WdfRequestComplete (or some variant of that function)
- Sent it to a Remote I/O Target using Send And Forget
- Forwarded it to another Queue
If that list looks familiar, it’s because it’s the exact same list of items that will trigger the release of a new Request from a Queue with Sequential Dispatching. Consistency is good, right? Given the above, if your driver’s been presented with a read Request, and that Request is currently in progress on your device, that Request will need to be accounted for by your driver before the Queue that presented that Request can be Stopped. Likewise, if you have a Request that you’ve sent asynchronously with WdfRequestSend, that Request will need to be accounted for by your driver before its Queue can be Stopped.
Accounting For In-Progress Requests
How does a driver properly “account for” Requests that are active when a device power state transition is pending? Well, the traditional way has been to not worry much about it. That is, the traditional way of accounting for pending Requests during a power-state transition is to simply wait for the Requests to complete. When all the Requests that are active from the Queue are completed (by the driver calling WdfRequestComplete or similar), the Queue can be stopped and then the Framework will allow the system to finish transitioning the device to Dx.
Depending on your driver and device architecture, there can be some significant disadvantages to this traditional approach. For one, if your device takes any significant amount of time to complete its in-progress Requests, you could slow down the entire system’s transition to a lower power state. Not to mention the fact that no user wants to sit watching their tablet/ultrabook/notebook/PC while it takes its time suspending. Another disadvantage to the traditional “do nothing” approach is that there are lots of Requests that can remain in progress for a long or indefinite amount of time. Consider, for example, a read operation from a serial data device. The read won’t complete until data has arrived. Until that time, the Request sits and waits – in progress the entire time.
And this, my friends, is where the EvtIoStop Event Processing Callback comes in. When it attempts to transition a WDF Queue to Stopped state, the Framework calls EvtIoStop for each active Request that’s been presented to the driver from a power managed Queue. Within its EvtIoStop function, the driver “accounts for” the Request by doing one of the following things:
- Making the Request no longer active, by:
- Completing the Request with whatever status makes sense (can be success or failure).
- Successfully sending the Request to an I/O Target using the Send And Forget option.
- Successfully forwarding the Request to another WDF Queue belonging to the device.
- Asking the Framework to put the Request back on the Queue from which it came. The driver can do this by calling WdfRequestStopAcknowledge with the Requeue set to TRUE.
- Telling the Framework that it intends to keep the Request in progress by calling WdfRequestStopAcknowledge with the Requeue parameter set to FALSE.
- Successfully canceling the Request by calling WdfRequestCancelSentRequest if the Request has been previous sent to an I/O Target without using the Send And Forget option.
Calling you at EvtIoStop is the Framework’s way of reminding you of the Requests that you have in-progress from a given Queue, before that Queue can become Stopped and the device can transition to Dx. Within your EvtIoStop routine you handle (or tell the Framework to handle) each pending Request. In this way, each active Request is “accounted for” in a timely way, no Requests are “lost” across a power-down event, and the device’s transition to a lower-powered D-State is not delayed.
In general, the way to avoid the dreaded bugcheck 0x9F is to be sure you implement an EvtIoStop Event Processing Callback for each of your Queues. Doing this is simple. You just specify your callback as part of your WDF_IO_QUEUE_CONFIG structure as shown in Figure 2.
EvtIoStop is Optional
Do you always need to specify an EvtIoStop Event Processing Callback in your driver? The answer is no. While it is never a mistake to do so, there are cases when don’t need to implement EvtIoStop. This is where the long and obtuse comment and _Analysis_assume in the WDK samples comes in. If your driver:
- Completes every Request it receives synchronously – that is, it never holds any Request in progress, or
- Receives Requests from a WDF Queue that is not power managed, or
- Guarantees that every Request that will be in progress when a device is asked to transition to a lower-powered D-State will always complete quickly…
…then your driver does not need to supply an EvtIoStop Event Processing Callback.
The most controversial of the above cases is the last one: When your driver can guarantee that every in-progress Request will complete quickly. The central issue here is what “quickly” should mean. In this case, we mean really fast… probably less than a second. Remember: The system might be waiting for your device to complete its transition to a lower-powered D-State, and for that to happen your active Requests have to complete. While the system provides for a lot longer timeout than one second before generating the 0x9F bug check, the goal is to provide a good user experience when the system is suspending or hibernating. Keeping your Request completion times short helps with this.
By default, SDV requires that every driver that utilizes a power managed Queue provide an EvtIoStop Event Processing Callback for that Queue. If you want your driver to pass SDV clean, with no warnings (and you should) then you’ll need to stop SDV from complaining. The easiest way to do this is via the _Analysis_assume shown in Figure 1. What this does is tell SDV to just assume that we have specified an EvtIoStop Event Processing Callback… even though we haven’t.
Power, Queues, and Requests
So that’s the story of how device power state transitions are related to WDF Queues, and how the need to stop WDF Queues relates to having an EvtIoStop Event Processing Callback in your driver. This is also an example of how requirements, and what’s considered “best practice” among developers, change over the years. The traditional method of handling Requests during transitions to a lower power state was “just let them finish and otherwise don’t worry about it.” This was true even in the earlier days of WDF. But as more emphasis is placed on battery performance, portable systems often sleep a lot more frequently. In this case “just let them finish” can be a bad policy. Not to mention, losing I/O requests across power state transitions wasn’t ever a good thing.
With a properly considered policy, and by implementing EvtIoStop when required, your driver will be more likely to handle device power state transitions properly.