www.pudn.com > usbfx2lk_v1.1.zip > usbfx2lk_selsusp.cpp
///////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright 2005 OSR Open Systems Resources, Inc.
// Copyright (c) 2000 Microsoft Corporation
// All Rights Reserved
//
// This sofware is supplied for instructional purposes only.
//
// OSR Open Systems Resources, Inc. (OSR) expressly disclaims any warranty
// for this software. THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
// THE IMPLIED WARRANTIES OF MECHANTABILITY OR FITNESS FOR A PARTICULAR
// PURPOSE. THE ENTIRE RISK ARISING FROM THE USE OF THIS SOFTWARE REMAINS
// WITH YOU. OSR's entire liability and your exclusive remedy shall not
// exceed the price paid for this material. In no event shall OSR or its
// suppliers be liable for any damages whatsoever (including, without
// limitation, damages for loss of business profit, business interruption,
// loss of business information, or any other pecuniary loss) arising out
// of the use or inability to use this software, even if OSR has been
// advised of the possibility of such damages. Because some states/
// jurisdictions do not allow the exclusion or limitation of liability for
// consequential or incidental damages, the above limitation may not apply
// to you.
//
// OSR Open Systems Resources, Inc.
// 105 Route 101A Suite 19
// Amherst, NH 03031 (603) 595-6500 FAX: (603) 595-6503
// email bugs to: bugs@osr.com
//
//
// MODULE:
//
// USBFx2LK_selsusp.cpp
//
// ABSTRACT:
//
// This file contains the routines that handle selective suspend
// processing for the OSR USB FX2 Learning Kit Device
//
// AUTHOR(S):
//
// OSR Open Systems Resources, Inc.
//
///////////////////////////////////////////////////////////////////////////////
#include "usbfx2lk.h"
#ifdef WPP_TRACING
//
// Include the necessary tmh file - this is
// just a matter of course if you're using WPP tracing.
//
extern "C" {
#include "usbfx2lk_selsusp.tmh"
}
#endif
//
// Forward declarations
//
#ifndef WIN2K
VOID SSUsbIdleCallback(PVOID Context);
NTSTATUS SSIdleNotificationCompletionRoutine(PDEVICE_OBJECT DeviceObject,
PIRP Irp, PVOID Context);
#endif
VOID SSPowerUpCompleteUpdateSSState(PUSBFX2LK_EXT DevExt);
VOID SSSynchronousPowerIrpCompletionFunc(PDEVICE_OBJECT DeviceObject,
UCHAR MinorFunction,
POWER_STATE PowerState,
PVOID Context,
PIO_STATUS_BLOCK IoStatus);
VOID SSAsynchronousPowerUpIrpCompletionFunc(PDEVICE_OBJECT DeviceObject,
UCHAR MinorFunction,
POWER_STATE PowerState,
PVOID Context,
PIO_STATUS_BLOCK IoStatus);
///////////////////////////////////////////////////////////////////////////////
//
// EnableSelectiveSuspend
//
// Enables selective suspend on the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// An appropriate NTSTATUS value
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID EnableSelectiveSuspend(PUSBFX2LK_EXT DevExt)
{
KIRQL oldIrql;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("EnableSelectiveSuspend: Entered.\n"));
//
// We're potentially going to change the state of the selective
// suspend members of our device extension, so we must acquire
// the selective suspend lock
//
KeAcquireSpinLock(&DevExt->SSLock,&oldIrql);
//
// ASSERT that we are not suspended or in the process of
// suspending. Our logic is written such that we assume that
// we are fully powered while enabling/disabling SS for the
// device. This is done by the calling function calling
// SSPowerDeviceIfSuspended before calling us
//
ASSERT(DevExt->SSState == SS_NOT_STARTED);
ASSERT(KeReadStateEvent(&DevExt->SSDeviceNotSuspendedEvent));
//
// If the user doesn't ever want selective suspend, then
// we can just get out of here
//
if (!DevExt->SSEnabledByUser) {
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("EnableSelectiveSuspend: SS disabled by user. Exiting\n"));
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
return;
}
//
// See if we've already been here before. If so, we
// can just exit
//
if (DevExt->SSInitialized) {
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("EnableSelectiveSuspend: SS already initialized. Exiting\n"));
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
return;
}
//
// Default to not allowing the device to suspend right
// now. Because we're called in this routine only as the
// result of an I/O request (either from the User or from
// the PnP manager) the I/O count is currently elevated.
// When it drops to 0, we will set this to TRUE
//
DevExt->SSDeviceCanNowSuspend = FALSE;
//
// We're ready to selectively suspend!
//
DevExt->SSInitialized = TRUE;
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("EnableSelectiveSuspend: Exit.\n"));
return;
}
///////////////////////////////////////////////////////////////////////////////
//
// DisableSelectiveSuspend
//
// Disables selective suspend on the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// An appropriate NTSTATUS value
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID DisableSelectiveSuspend(PUSBFX2LK_EXT DevExt)
{
KIRQL oldIrql;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("DisableSelectiveSuspend: Entered.\n"));
//
// We're potentially going to change the state of the selective
// suspend members of our device extension, so we must acquire
// the selective suspend lock
//
KeAcquireSpinLock(&DevExt->SSLock,&oldIrql);
//
// ASSERT that we are not suspended or in the process of
// suspending. Our logic is written such that we assume that
// we are fully powered while enabling/disabling SS for the
// device. This is done by the calling function calling
// SSPowerDeviceIfSuspended before calling us
//
ASSERT(DevExt->SSState == SS_NOT_STARTED);
ASSERT(KeReadStateEvent(&DevExt->SSDeviceNotSuspendedEvent));
//
// Note that we're called here both when the user doesn't
// want SS anymore AND when SS just needs to be disabled
// so that the device can stop or be removed.
//
//
// See if we've already been here before. If so, we
// can just exit
//
if (!DevExt->SSInitialized) {
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("DisableSelectiveSuspend: SS not initialized. Exiting\n"));
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
return;
}
//
// The device can't suspend right now...
//
DevExt->SSDeviceCanNowSuspend = FALSE;
//
// And it won't suspend in the future...
//
DevExt->SSInitialized = FALSE;
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("DisableSelectiveSuspend: Exit.\n"));
return;
}
///////////////////////////////////////////////////////////////////////////////
//
// SSPowerDeviceIfSuspended
//
// This routine synchronously powers the device up if it is
// suspended or in the process of being suspended
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// An appropriate NTSTATUS value
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
// This routine synchronously powers the device and therefore
// MUST be called at PASSIVE_LEVEL.
//
///////////////////////////////////////////////////////////////////////////////
VOID SSPowerDeviceIfSuspended(PUSBFX2LK_EXT DevExt)
{
PAGED_CODE();
//
// See if the device is currently suspended. If it
// is, this routine will attempt to asynchronously power
// the device back up and will return TRUE. In that
// case, we want to synchronously wait for the power up
// to occur.
//
// However, if this routine returns FALSE the device is
// already powered up and we can simply return
//
if (SSPowerDeviceIfSuspendedAsync(DevExt)) {
//
// Wait for the suspend to be over
//
(VOID)OsrWaitForSingleObject(&DevExt->SSDeviceNotSuspendedEvent);
}
return;
}
///////////////////////////////////////////////////////////////////////////////
//
// SSPowerDeviceIfSuspendedAsync
//
// This routine checks to see if the device is suspended or in the
// process of being suspsended and takes the appropriate action
// to get the device powered again.
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// TRUE if the device is suspended, FALSE if the device is
// fully powered
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
//
///////////////////////////////////////////////////////////////////////////////
BOOLEAN SSPowerDeviceIfSuspendedAsync(PUSBFX2LK_EXT DevExt)
{
BOOLEAN queuePowerUp = FALSE;
BOOLEAN powerUpQueued = FALSE;
KIRQL oldIrql;
#ifndef W2K
PIRP idleIrp = NULL;
#endif
PAGED_CODE();
//
// We'll need the SS lock for this
//
KeAcquireSpinLock(&DevExt->SSLock,&oldIrql);
//
// Only if SS is setup to we need to do any
// checks.
//
if (DevExt->SSInitialized) {
//
// Indicate that we do not want the device to suspend
// because we're about to send a request to it.
//
DevExt->SSDeviceCanNowSuspend = FALSE;
//
// Check the state.
//
switch (DevExt->SSState) {
case SS_SUSPENDED: {
DevExt->SSState = SS_PROCESSING;
//
// We'll be powering up...
//
queuePowerUp = TRUE;
break;
}
case SS_PROCESSING: {
powerUpQueued = TRUE;
#ifndef W2K
//
// On XP and later, we need to submit and idle
// notification IRP to the bus driver and wait
// to be called back in order to suspend. If we're
// on a hub with a device that is not suspending,
// this request may still be pending. If so, we
// need to cancel it before we can do anything
//
//
// Grab the idle IRP out of the extension
//
idleIrp = DevExt->SSIdleIrp;
//
// And set it to NULL. This is the clue to the
// completion routine that we will be freeing
// the IRP
//
DevExt->SSIdleIrp = NULL;
#endif
break;
}
case SS_NOT_STARTED: {
//
// Nothing to do here, we're powered up and active
// already
//
break;
}
default: {
//
// Shouldn't be here...
//
ASSERT(FALSE);
break;
}
}
} else {
//
// If SS is not initialized, we should NEVER
// be called here and not at D0. If so, then
// the caller either didn't check the PnP state
// before calling here or the PnP state machine
// is broken.
//
ASSERT(DevExt->DevicePowerState == PowerDeviceD0);
}
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
#ifndef W2K
//
// If we got an idle IRP, we're going to need to
// cancel it
//
if (idleIrp) {
//
// Cancel the IRP
//
IoCancelIrp(idleIrp);
//
// Wait for the IRP to finish
//
(VOID)OsrWaitForSingleObject(&DevExt->SSIdleCompletionRoutineCalled);
//
// We got the IRP, so we free it.
//
IoFreeIrp(idleIrp);
}
#endif
if (queuePowerUp) {
//
// We need to power the device ourselves
//
SSPowerUpDeviceAsync(DevExt);
}
//
// If we queued the power up IRP or there
// was already a power up queued, indicate
// that the device is suspended by returning
// TRUE
//
return (queuePowerUp || powerUpQueued);
}
///////////////////////////////////////////////////////////////////////////////
//
// SSSubmissionThreadRoutine
//
// This thread runs while the device is attached to the system. Its goal
// in life is to wake up when the device no longer has any active I/O
// and attempt to suspend it
//
// INPUTS:
//
// Context - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// System context
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSSubmissionThreadRoutine(PVOID Context)
{
PUSBFX2LK_EXT devExt = (PUSBFX2LK_EXT)Context;
NTSTATUS status;
PVOID waitObjects[2];
LARGE_INTEGER delay;
LARGE_INTEGER currentTime;
LARGE_INTEGER timeSinceIdle;
KIRQL oldIrql;
BOOLEAN powerDeviceBackUp;
BOOLEAN restartQueues;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSubmissionThreadRoutine: Entered.\n"));
//
// Define an arbitrary thread delay that we'll add to
// our loop.
//
delay.QuadPart = RELATIVE(SECONDS(1));
//
// Wait on the STOP event, which is signaled when the oustanding
// I/O count on the device drops to 1 and also the
// thread termination event,
//
waitObjects[0] = &devExt->StopEvent;
waitObjects[1] = &devExt->SSSubmissionThreadTerminateEvent;
//
// Loop forever...
//
while (TRUE) {
//
// Wait on our above two events.
//
// We don't use OsrWaitForMultipleObjects here
// because we expect to be just sit here for a
// loooong time and the otherwise helpful debug output
// produced by OsrWaitForMultipleObjects just becomes
// noise in this case
//
status = KeWaitForMultipleObjects(2,
waitObjects,
WaitAny,
Executive,
KernelMode,
FALSE,
NULL,
NULL);
//
// We expect a wait status indicating that either
// the stop event was fired OR that the
// terminate thread event was fired. Anything else is an
// error.
//
if (status != STATUS_WAIT_0) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("EnableSelectiveSuspend: KeWaitForMultipleObjects returned "\
"0x%x (%s). Exiting...\n",
status,OsrNtStatusToString(status)));
PsTerminateSystemThread(status);
return;
}
//
// Default to not needing to power the device up
// or restarting the queues, let the logic below
// change this as necessary.
//
powerDeviceBackUp = FALSE;
restartQueues = FALSE;
//
// Grab the current time. We'll use this to determine if
// the device has been idle long enough.
//
KeQuerySystemTime(¤tTime);
//
// We need to try to suspend the device
//
KeAcquireSpinLock(&devExt->SSLock,&oldIrql);
//
// If the device can suspend right now AND
// we haven't been suspended, we'll do the power
// down.
//
// Not that while the device is suspended, the stop
// event stays fired so we'll keep coming back here
// every "delay" seconds and check it. This isn't an
// optimal solution, as it requires polling even if
// the device is already idle or SS is disabled by the
// user, but it does provide for a fairly straightforward
// SS implementation. SS is very difficult to get correct,
// so we have chosen to not attempt to optimize and provide
// something that is safe instead. Whether or not this is
// acceptable to your application is a design decision
// that you will have to make.
//
if (devExt->SSDeviceCanNowSuspend &&
devExt->SSState == SS_NOT_STARTED) {
//
// Calculate how long it has been since the
// device became idle
//
timeSinceIdle.QuadPart =
currentTime.QuadPart -
devExt->SSTimeOfLastActivity.QuadPart;
//
// Is the difference sufficient to constitute an idle
// device?
//
if (timeSinceIdle.QuadPart >= SS_DEVICE_IDLE_DELAY) {
//
// We're about to power down the device, set the state
// to SS_PROCESSING
//
devExt->SSState = SS_PROCESSING;
//
// Clear the "device is out of selective suspend" event
//
KeClearEvent(&devExt->SSDeviceNotSuspendedEvent);
//
// We don't need this again until the device
// has suspended. The fact that we have
// set the state to SS_PROCESSING will
// cause any arriving operations that need the
// device fully powered to queue (we'll deal
// with getting them started again once we've processed
// the power down).
//
// Note that we're being overly cautious and a bit
// obtuse with our selective suspend algorithm here.
// If an operation comes in at this point we don't
// try to cancel the in progress power down. Instead,
// we wait for the device to power all the way down
// and then we'll power it back up again before restarting
// the queues. This is a safe approach (as it eliminates
// many race conditions) but it may not be ideal for
// your environment.
//
KeReleaseSpinLock(&devExt->SSLock,oldIrql);
//
// Synchronously suspend the device.
//
#ifndef W2K
//
// If we're not built for Windows 2000 compatibility,
// we can't just power the device down, we need to let
// the USB hus driver know about it first. In this case
// we'll call SSSuspendDeviceWithCallback which will
// send the appropriate IRP and power down from within
// the callback
//
SSSuspendDeviceWithCallback(devExt);
#else
//
// Otherwise, there is no concept of notifiying the bus
// driver first. So, just power the device down inline
//
SSSuspendDevice(devExt);
#endif
//
// Acquire our lock again and see if the state of the
// world has changed. If not, then we'll set our
// SS state to be SUSPENDED. If it HAS changed, then
// we need to power up the device again
//
KeAcquireSpinLock(&devExt->SSLock,&oldIrql);
//
// Make sure that the power down actually succeeded.
// If not, then there's nothing to be done
//
if (devExt->DevicePowerState != devExt->PowerDownLevel) {
OsrTracePrint(TRACE_LEVEL_ERROR,OSRDBG_SELECTIVE_SUSPEND,
("SSSubmissionThreadRoutine: Power down of device did"\
" not succeed. Device power state is 0x%x\n",
devExt->DevicePowerState));
devExt->SSState = SS_NOT_STARTED;
//
// Set the "device is now out of selective suspend" event
//
KeSetEvent(&devExt->SSDeviceNotSuspendedEvent,
EVENT_INCREMENT,
FALSE);
//
// Restart the queues that were stalled
// as part of the powering down
//
restartQueues = TRUE;
} else {
//
// Is everyone still OK with us suspending the device?
//
if (devExt->SSDeviceCanNowSuspend == TRUE) {
//
// Yup, no one wants the device just yet sp
// the Device is now suspended!
//
devExt->SSState = SS_SUSPENDED;
} else {
//
// Whoops. While we were powering down, a request
// came in that required access to the device.
// Power back up again once we've dropped the lock
//
powerDeviceBackUp = TRUE;
}
}
//
// Lock acquisition above isn't leaked, it's released by
// the upcoming KeRelease outside of the original IF
//
}
}
KeReleaseSpinLock(&devExt->SSLock,oldIrql);
if (powerDeviceBackUp) {
//
// While we were powering the device down,
// a request came in that needed the device
// to be powered. Power the device back up
// here.
//
//
// We never leave the "selective suspend
// processing" state in this case, which
// allows us to queue all of those operations
// that requested the power up.
//
// The power up code deals with getting the
// queues restarted by calling OsrProcessQueuedRequests
//
ASSERT(devExt->SSState == SS_PROCESSING);
SSPowerUpDevice(devExt);
}
if (restartQueues) {
//
// The suspend failed for whatever reason and the
// queues need to be restarted
//
OsrProcessQueuedRequests(devExt);
}
//
// Put a bit of a delay in. Because the stop
// event is a notification event, we'll keep
// hitting this loop while suspended so
// the delay just leaves some breathing room.
//
KeDelayExecutionThread(KernelMode,
FALSE,
&delay);
}
return;
}
///////////////////////////////////////////////////////////////////////////////
//
// SSPowerUpDevice
//
// This routine synchronously powers up the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
//
///////////////////////////////////////////////////////////////////////////////
VOID SSPowerUpDevice(PUSBFX2LK_EXT DevExt)
{
POWER_STATE devicePowerState;
KEVENT powerUpCompleteEvent;
NTSTATUS status;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDevice: Entered\n"));
//
// We should not already be in D0 at this point
//
ASSERT(DevExt->DevicePowerState != PowerDeviceD0);
//
// Submit a device power IRP to our device
//
//
// This constitutes a new I/O on the device,
// so bump the oustanding I/O count
//
OsrIncrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
//
// Setup the power up state to be D0
//
devicePowerState.DeviceState = PowerDeviceD0;
//
// Initialize the power up event that our power completion
// routine will signal when the power up is complete
//
KeInitializeEvent(&powerUpCompleteEvent, NotificationEvent, FALSE);
//
// And ask the power manager to create and send a power up D-IRP
// to our PDO. Specify the synchronous power completion routine,
// which will simply set the event that we pass to it when it
// runs
//
status = PoRequestPowerIrp(DevExt->PhysicalDeviceObject,
IRP_MN_SET_POWER,
devicePowerState,
SSSynchronousPowerIrpCompletionFunc,
&powerUpCompleteEvent,
NULL);
//
// We want to do this operation synchronously, so just wait
// for the operation to complete
//
if (status == STATUS_PENDING) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDevice: Waiting for the power irp to complete\n"));
OsrWaitForSingleObject(&powerUpCompleteEvent);
}
//
// The I/O is finished.
//
OsrDecrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDevice: PoRequestPowerIrp returned 0x%x (%s).\n",
status,OsrNtStatusToString(status)));
//
// A power up is complete, get the Selective Suspend
// state properly updated
//
SSPowerUpCompleteUpdateSSState(DevExt);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDevice: Exited\n"));
}
///////////////////////////////////////////////////////////////////////////////
//
// SSPowerUpCompleteUpdateSSState
//
// This routine updates the Selective Suspend state of the device
// after a power up operation is complete
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL <= DISPATCH_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
//
///////////////////////////////////////////////////////////////////////////////
VOID SSPowerUpCompleteUpdateSSState(PUSBFX2LK_EXT DevExt)
{
KIRQL oldIrql;
//
// If someone has called here then we've just finished
// a power up so we need to update the SS state
// based on whether or not the power up actually
// happened
//
KeAcquireSpinLock(&DevExt->SSLock,&oldIrql);
if (DevExt->DevicePowerState != PowerDeviceD0) {
OsrTracePrint(TRACE_LEVEL_ERROR,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDevice: Power up of device did not succeed. Device "\
"power state is 0x%x\n",
DevExt->DevicePowerState));
//
// Indicate that we're suspended.
//
DevExt->SSState = SS_SUSPENDED;
} else {
//
// Device is now powered up!
//
DevExt->SSState = SS_NOT_STARTED;
}
//
// Set the "device is now out of selective suspend" event
//
KeSetEvent(&DevExt->SSDeviceNotSuspendedEvent,
EVENT_INCREMENT,
FALSE);
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
//
// Restart any requests that may have been queued
// while the device was powering down.
//
OsrProcessQueuedRequests(DevExt);
}
///////////////////////////////////////////////////////////////////////////////
//
// SSPowerUpDeviceAsync
//
// This routine asynchronously powers up the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
//
///////////////////////////////////////////////////////////////////////////////
VOID SSPowerUpDeviceAsync(PUSBFX2LK_EXT DevExt)
{
POWER_STATE devicePowerState;
NTSTATUS status;
KIRQL oldIrql;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDeviceAsync: Entered\n"));
//
// We should not already be in D0 at this point
//
ASSERT(DevExt->DevicePowerState != PowerDeviceD0);
//
// Submit a device power IRP to our device
//
//
// This constitutes a new I/O on the device,
// so bump the oustanding I/O count
//
OsrIncrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
//
// Setup the power up state to be D0
//
devicePowerState.DeviceState = PowerDeviceD0;
//
// And ask the power manager to create and send a power up D-IRP
// to our PDO. Specify the asynchronous power completion routine
// which will deal with getting the SS state updated when the
// IRP is completed.
//
status = PoRequestPowerIrp(DevExt->PhysicalDeviceObject,
IRP_MN_SET_POWER,
devicePowerState,
SSAsynchronousPowerUpIrpCompletionFunc,
DevExt,
NULL);
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDeviceAsync: PoRequestPowerIrp returned 0x%x (%s).\n",
status,OsrNtStatusToString(status)));
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSPowerUpDeviceAsync: Exited\n"));
}
///////////////////////////////////////////////////////////////////////////////
//
// SSSuspendDevice
//
// This routine synchronously suspends the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSSuspendDevice(PUSBFX2LK_EXT DevExt)
{
POWER_STATE devicePowerState;
KEVENT powerDownCompleteEvent;
NTSTATUS status;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDevice: Entered\n"));
//
// If we haven't figured out the power state
// that we're going to suspend to then we can't
// suspend. This may happen while the device is first starting
// up.
//
// Also, if we're already powered down there's no point in
// trying to power down again.
//
if (DevExt->PowerDownLevel == PowerDeviceUnspecified ||
DevExt->DevicePowerState != PowerDeviceD0) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDevice: PowerDownLevel not set or already "\
"suspended, not suspending\n"));
return;
}
//
// Submit a device power IRP to our device
//
//
// This constitutes a new I/O on the device,
// so bump the oustanding I/O count
//
OsrIncrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
//
// Setup the power down state to be the state
// that we determined during the QUERY_CAPABILITIES
// processing
//
devicePowerState.DeviceState = (DEVICE_POWER_STATE)DevExt->PowerDownLevel;
//
// Initialize the power down event that our power completion
// routine will signal when the power down is complete
//
KeInitializeEvent(&powerDownCompleteEvent, NotificationEvent, FALSE);
//
// And ask the power manager to create and send a power down D-IRP
// to our PDO. We always power the device down synchronously, so
// specify the synchronous power completion routine and pass
// an event to be set.
//
status = PoRequestPowerIrp(DevExt->PhysicalDeviceObject,
IRP_MN_SET_POWER,
devicePowerState,
SSSynchronousPowerIrpCompletionFunc,
&powerDownCompleteEvent,
NULL);
//
// We want to do this operation synchronously, so just wait
// for the operation to complete
//
if (status == STATUS_PENDING) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDevice: Waiting for the power irp to complete\n"));
OsrWaitForSingleObject(&powerDownCompleteEvent);
}
//
// The I/O is finished.
//
OsrDecrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDevice: PoRequestPowerIrp returned 0x%x (%s). Exiting...\n",
status,OsrNtStatusToString(status)));
//
// Unlike the power UP case, we don't try to determine if the power
// down actually happened and update the SS state. This is because
// in the power up case there's no concept of, "hey, I didn't want that
// power up!" as there is in the power down case. So, it is the caller's
// responsiblity to determine that if at any point during the power down
// the power down request was cancelled and that the device actually
// needs to be powered up again. Basically, more decisions need to be
// made before we can get out of the SS_PROCESSING state so we just
// return to the caller and let him deal with it.
//
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDevice: Exited\n"));
}
#ifndef W2K
///////////////////////////////////////////////////////////////////////////////
//
// SSSuspendDeviceWithCallback
//
// This routine requests an idle notification callback from the bus driver
// and uses it to synchronously power down the device
//
// INPUTS:
//
// DevExt - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSSuspendDeviceWithCallback(PUSBFX2LK_EXT DevExt)
{
PIRP idleCallbackIrp;
PIO_STACK_LOCATION nextStack;
NTSTATUS status;
KIRQL oldIrql;
PVOID waitObjects[2];
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Entered\n"));
//
// We're built for XP and later, so we need to
// submit an idle callback request and wait
// for the callback to be called by the bus
// driver before powering down the device
//
//
// There's one REALLY annoying side effect of us
// having to go through this path:
//
// ** There's no guarantee that the IRP won't complete
// ** in error before, DURING, or after the idle
// ** callback running. Also, in the successful
// ** case, the IRP's completion routine doesn't
// ** run until the device is powered back up again
//
// So, the idle callback may or may not ever run and
// the IRP's completion routine may run now or
// 48 hours in the future. However, we still want
// to supply seemingly synchronous power down behaviour
// even when we require the use of the idle callback
// (again, SS is hard enough so we're not going to
// optimize).
//
// We'll use 2 events to do this. One that will be fired
// within the callback (which, in the normal case, will
// run within a relatively short time) and one event for
// the running of the IRP's completion routine.
//
// If the event from the callback routine gets fired,
// then the device is powered down and we're set. If the
// event from the completion routine gets fired, then the
// idle IRP was aborted by the bus driver for some reason.
//
//
// Set the events to a known state
//
KeClearEvent(&DevExt->SSIdleCallbackCalled);
KeClearEvent(&DevExt->SSIdleCompletionRoutineCalled);
//
// Fill in the idle callback structure that we need
// to pass along with the IRP
//
DevExt->SSIdleCallbackInfo.IdleCallback = SSUsbIdleCallback;
DevExt->SSIdleCallbackInfo.IdleContext = DevExt;
//
// This constitutes a new I/O on the device,
// so bump the oustanding I/O count
//
OsrIncrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
//
// And allocate an IRP of the appropriate size
//
idleCallbackIrp = IoAllocateIrp(DevExt->DeviceToSendIrpsTo->StackSize,
FALSE);
if (!idleCallbackIrp) {
OsrTracePrint(TRACE_LEVEL_ERROR,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: IoAllocateIrp returned NULL. Exiting...\n"));
return;
}
//
// Get the next stack location for the IRP and
// set it up to be an idle notification request
//
nextStack = IoGetNextIrpStackLocation(idleCallbackIrp);
nextStack->MajorFunction =
IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode =
IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;
nextStack->Parameters.DeviceIoControl.Type3InputBuffer =
&DevExt->SSIdleCallbackInfo;
nextStack->Parameters.DeviceIoControl.InputBufferLength =
sizeof(USB_IDLE_CALLBACK_INFO);
//
// Setup our completion routine that will free the memory
// for this IRP
//
IoSetCompletionRoutine(idleCallbackIrp,
SSIdleNotificationCompletionRoutine,
DevExt,
TRUE,
TRUE,
TRUE);
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Submitting idle IRP 0x%p.\n",
idleCallbackIrp));
//
// Set this IRP up in our device extension. We need to
// keep track of this IRP because it will pend
// forever if we are on a hub with a device that
// isn't suspending.
//
KeAcquireSpinLock(&DevExt->SSLock,&oldIrql);
//
// Shouldn't already have a request pending
//
ASSERT(DevExt->SSIdleIrp == NULL);
//
// There's another race condition that we have to contend
// with in this case. SSPowerDeviceIfSuspended may have
// already executed and looked to see if there is
// an Idle IRP pending. Because we haven't set the
// field yet, it will think that the device is just
// in the middle of powering up and will simply
// wait for the power up event. That means that we
// could sit here waiting for the idle notification
// callback to occur (which may not EVER happen) while
// the rest of our code is waiting for a device power
// up to happen, leading to a livelock in the driver.
//
// We'll cover this race condition by adding an extra
// check here to make sure that the device hasn't
// become busy while we were building the idle IRP.
// If it has, we'll just get out of here...
//
if (DevExt->SSDeviceCanNowSuspend == FALSE) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Device became busy while building "\
"IDLE IRP. Exiting.\n"));
//
// Free the IRP we built
//
IoFreeIrp(idleCallbackIrp);
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
//
// The I/O is finished.
//
OsrDecrementOutstandingIoCount(DevExt,__FILE__,__LINE__);
return;
}
//
// Setup a pointer to the idle IRP in the
// device extension
//
DevExt->SSIdleIrp = idleCallbackIrp;
KeReleaseSpinLock(&DevExt->SSLock,oldIrql);
//
// And submit the IRP down to the lower driver.
//
status = IoCallDriver(DevExt->DeviceToSendIrpsTo,
idleCallbackIrp);
if (NT_SUCCESS(status)) {
//
// If we got a successful status, wait for the
// idle callback to fire and the device to
// power down OR for the IRP's completion routine
// to be called because there was some sort of error
// that occurred before the device could idle
//
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Waiting for suspend idle callback "\
"or completion routine to fire...\n"));
waitObjects[0] = &DevExt->SSIdleCallbackCalled;
waitObjects[1] = &DevExt->SSIdleCompletionRoutineCalled;
//
// Wait on our above two events.
//
status = OsrWaitForMultipleObjects(2,
waitObjects,
WaitAny,
NULL);
if (status == STATUS_WAIT_0) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Idle callback fired!\n"));
} else if (status == STATUS_WAIT_1) {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Completion routine fired!\n"));
} else {
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: KeWaitForMultipleObjects "\
"returned unknown status 0x%x (%s).\n",
status,OsrNtStatusToString(status)));
}
} else {
//
// The IRP was failed before the idle callback was
// queued. Not much we can do, the completion routine
// will free the IRP
//
OsrTracePrint(TRACE_LEVEL_INFORMATION,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Idle request failed, bus returned "\
"0x%x (%s). Exiting...\n",
status,OsrNtStatusToString(status)));
}
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSuspendDeviceWithCallback: Exited\n"));
}
///////////////////////////////////////////////////////////////////////////////
//
// SSUsbIdleCallback
//
// This routine is the USB idle callback that does the actually
// suspending of the device when built for XP and later
//
// INPUTS:
//
// Context - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL == PASSIVE_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSUsbIdleCallback(PVOID Context)
{
PUSBFX2LK_EXT devExt = (PUSBFX2LK_EXT)Context;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSUsbIdleCallback: Entered\n"));
//
// And now power the device down
//
SSSuspendDevice(devExt);
KeSetEvent(&devExt->SSIdleCallbackCalled,
EVENT_INCREMENT,
FALSE);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSUsbIdleCallback: Exited\n"));
}
///////////////////////////////////////////////////////////////////////////////
//
// SSIdleNotificationCompletionRoutine
//
// This routine is the completion routine for the IRP used to submit the
// USB idle callback
//
// INPUTS:
//
// DeviceObject - We allocated the IRP, so this is NULL
//
// Irp - The idle notification IRP
//
// Context - One of our device extensions
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// STATUS_MORE_PROCESSING_REQUIRED
//
// IRQL:
//
// IRQL <= DISPATCH_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
NTSTATUS SSIdleNotificationCompletionRoutine(PDEVICE_OBJECT DeviceObject,
PIRP Irp, PVOID Context)
{
PUSBFX2LK_EXT devExt = (PUSBFX2LK_EXT)Context;
KIRQL oldIrql;
PIRP idleIrp;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSIdleNotificationCompletionRoutine: Entered\n"));
KeAcquireSpinLock(&devExt->SSLock,&oldIrql);
//
// Let's see if someone else didn't get to this
// first
//
idleIrp = devExt->SSIdleIrp;
devExt->SSIdleIrp = NULL;
KeReleaseSpinLock(&devExt->SSLock,oldIrql);
if (idleIrp) {
//
// If we got the IRP, we free it. Otherwise let the
// person who cancelled the IRP free it.
//
IoFreeIrp(idleIrp);
}
KeSetEvent(&devExt->SSIdleCompletionRoutineCalled,
EVENT_INCREMENT,
FALSE);
//
// The I/O is finished.
//
OsrDecrementOutstandingIoCount(devExt,__FILE__,__LINE__);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSIdleNotificationCompletionRoutine: Exited\n"));
return STATUS_MORE_PROCESSING_REQUIRED;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// SSSynchronousPowerIrpCompletionFunc
//
// Generic SYNCHRONOUS selective suspend D-IRP power completion routine
//
// INPUTS:
//
// DeviceObject - Not used
//
// MinorFunction - Not used
//
// PowerState - Not used
//
// Context - An event to signal the completion of the D-IRP
//
// IoStatus - Not used
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL <= DISPATCH_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSSynchronousPowerIrpCompletionFunc(PDEVICE_OBJECT DeviceObject,
UCHAR MinorFunction,
POWER_STATE PowerState,
PVOID Context,
PIO_STATUS_BLOCK IoStatus)
{
PKEVENT completeEvent = (PKEVENT)Context;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSynchronousPowerIrpCompletionFunc: Entered\n"));
KeSetEvent(completeEvent, EVENT_INCREMENT, FALSE);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSSynchronousPowerIrpCompletionFunc: Exiting\n"));
}
///////////////////////////////////////////////////////////////////////////////
//
// SSAsynchronousPowerUpIrpCompletionFunc
//
// ASYNCHRONOUS selective suspend for power up D-IRP completion
// routine
//
// INPUTS:
//
// DeviceObject - Not used
//
// MinorFunction - Not used
//
// PowerState - Not used
//
// Context - One of our device extensions
//
// IoStatus - Not used
//
// OUTPUTS:
//
// None
//
// RETURNS:
//
// None
//
// IRQL:
//
// IRQL <= DISPATCH_LEVEL
//
// CONTEXT:
//
// Arbitrary
//
// NOTES:
//
///////////////////////////////////////////////////////////////////////////////
VOID SSAsynchronousPowerUpIrpCompletionFunc(PDEVICE_OBJECT DeviceObject,
UCHAR MinorFunction,
POWER_STATE PowerState,
PVOID Context,
PIO_STATUS_BLOCK IoStatus)
{
PUSBFX2LK_EXT devExt = (PUSBFX2LK_EXT)Context;
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSAsynchronousPowerUpIrpCompletionFunc: Entered\n"));
//
// The I/O is finished.
//
OsrDecrementOutstandingIoCount(devExt,__FILE__,__LINE__);
//
// A power up is complete, get the Selective Suspend
// state properly updated
//
SSPowerUpCompleteUpdateSSState(devExt);
OsrTracePrint(TRACE_LEVEL_VERBOSE,OSRDBG_SELECTIVE_SUSPEND,
("SSAsynchronousPowerUpIrpCompletionFunc: Exiting\n"));
}