/*++ Copyright (c) 1997 Microsoft Corporation Module Name: HUBPWR.C Abstract: This module contains functions to handle power irps to the hub PDOs and FDOs. Author: jdunn Environment: kernel mode only Notes: Revision History: 7-1-97 : created --*/ #include #ifdef WMI_SUPPORT #include #endif /* WMI_SUPPORT */ #include "usbhub.h" #ifdef PAGE_CODE #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, USBH_SetPowerD0) #pragma alloc_text(PAGE, USBH_SetPowerD1orD2) #pragma alloc_text(PAGE, USBH_PdoSetPower) #pragma alloc_text(PAGE, USBH_PdoPower) #pragma alloc_text(PAGE, USBH_IdleCompletePowerHubWorker) #pragma alloc_text(PAGE, USBH_CompletePortIdleIrpsWorker) #pragma alloc_text(PAGE, USBH_CompletePortWakeIrpsWorker) #pragma alloc_text(PAGE, USBH_HubAsyncPowerWorker) #pragma alloc_text(PAGE, USBH_IdleCancelPowerHubWorker) #endif #endif VOID USBH_CompletePowerIrp( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PIRP Irp, IN NTSTATUS NtStatus) /* ++ * * Description: * * This function complete the specified Irp with no priority boost. It also * sets up the IoStatusBlock. * * Arguments: * * Irp - the Irp to be completed by us NtStatus - the status code we want to * return * * Return: * * None * * -- */ { Irp->IoStatus.Status = NtStatus; PoStartNextPowerIrp(Irp); USBH_DEC_PENDING_IO_COUNT(DeviceExtensionHub); IoCompleteRequest(Irp, IO_NO_INCREMENT); return; } NTSTATUS USBH_SetPowerD3( IN PIRP Irp, IN PDEVICE_EXTENSION_PORT DeviceExtensionPort ) /*++ Routine Description: Put the PDO in D3 Arguments: DeviceExtensionPort - port PDO deviceExtension Irp - Power Irp. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; KIRQL irql; PIRP hubWaitWake = NULL; LONG pendingPortWWs; PIRP idleIrp = NULL; PIRP waitWakeIrp = NULL; USBH_KdPrint((2,"'PdoSetPower D3\n")); deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; portNumber = DeviceExtensionPort->PortNumber; LOGENTRY(LOG_PNP, "spD3", deviceExtensionHub, DeviceExtensionPort->DeviceState, 0); if (DeviceExtensionPort->DeviceState == PowerDeviceD3) { // already in D3 USBH_KdPrint((0,"'PDO is already in D3\n")); ntStatus = STATUS_SUCCESS; goto USBH_SetPowerD3_Done; } // // Keep track of what PNP thinks is the current power state of the // port is. Do this now so that we will refuse another WW IRP that may be // posted after the cancel below. // DeviceExtensionPort->DeviceState = PowerDeviceD3; // // kill our wait wake irp // // we take the cancel spinlock here to ensure our cancel routine does // not complete the irp for us. // IoAcquireCancelSpinLock(&irql); if (DeviceExtensionPort->IdleNotificationIrp) { idleIrp = DeviceExtensionPort->IdleNotificationIrp; DeviceExtensionPort->IdleNotificationIrp = NULL; DeviceExtensionPort->PortPdoFlags &= ~PORTPDO_IDLE_NOTIFIED; IoSetCancelRoutine(idleIrp, NULL); LOGENTRY(LOG_PNP, "IdlX", deviceExtensionHub, DeviceExtensionPort, idleIrp); USBH_KdPrint((1,"'PDO %x going to D3, failing idle notification request IRP %x\n", DeviceExtensionPort->PortPhysicalDeviceObject, idleIrp)); } if (DeviceExtensionPort->PortPdoFlags & PORTPDO_REMOTE_WAKEUP_ENABLED) { LOGENTRY(LOG_PNP, "cmWW", deviceExtensionHub, DeviceExtensionPort->WaitWakeIrp, 0); USBH_KdPrint((1,"'Power state is incompatible with wakeup\n")); if (DeviceExtensionPort->WaitWakeIrp) { waitWakeIrp = DeviceExtensionPort->WaitWakeIrp; DeviceExtensionPort->WaitWakeIrp = NULL; DeviceExtensionPort->PortPdoFlags &= ~PORTPDO_REMOTE_WAKEUP_ENABLED; if (waitWakeIrp->Cancel || IoSetCancelRoutine(waitWakeIrp, NULL) == NULL) { waitWakeIrp = NULL; // Must decrement pending request count here because // we don't complete the IRP below and USBH_WaitWakeCancel // won't either because we have cleared the IRP pointer // in the device extension above. USBH_DEC_PENDING_IO_COUNT(deviceExtensionHub); } pendingPortWWs = InterlockedDecrement(&deviceExtensionHub->NumberPortWakeIrps); if (0 == pendingPortWWs && deviceExtensionHub->PendingWakeIrp) { hubWaitWake = deviceExtensionHub->PendingWakeIrp; deviceExtensionHub->PendingWakeIrp = NULL; } } } // // Finally, release the cancel spin lock // IoReleaseCancelSpinLock(irql); if (idleIrp) { idleIrp->IoStatus.Status = STATUS_POWER_STATE_INVALID; IoCompleteRequest(idleIrp, IO_NO_INCREMENT); } if (waitWakeIrp) { USBH_CompletePowerIrp(deviceExtensionHub, waitWakeIrp, STATUS_POWER_STATE_INVALID); } // // If there are no more outstanding WW irps, we need to cancel the WW // to the hub. // if (hubWaitWake) { USBH_HubCancelWakeIrp(deviceExtensionHub, hubWaitWake); } // // first suspend the port, this will cause the // device to draw minimum power. // // we don't turn the port off because if we do we // won't be able to detect plug/unplug. // USBH_SyncSuspendPort(deviceExtensionHub, portNumber); // // note that powering off the port disables connect/disconnect // detection by the hub and effectively removes the device from // the bus. // DeviceExtensionPort->PortPdoFlags |= PORTPDO_NEED_RESET; RtlCopyMemory(&DeviceExtensionPort->OldDeviceDescriptor, &DeviceExtensionPort->DeviceDescriptor, sizeof(DeviceExtensionPort->DeviceDescriptor)); USBH_KdPrint((1, "'Setting HU pdo(%x) to D3, status = %x complt\n", DeviceExtensionPort->PortPhysicalDeviceObject, ntStatus)); USBH_SetPowerD3_Done: USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); return ntStatus; } NTSTATUS USBH_HubSetD0Completion( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus ) /*++ Routine Description: Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus; PKEVENT pEvent = Context; KeSetEvent(pEvent, 1, FALSE); ntStatus = IoStatus->Status; return ntStatus; } NTSTATUS USBH_HubSetD0( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub ) /*++ Routine Description: Set the hub to power state D0 Arguments: DeviceExtensionPort - Hub FDO deviceExtension Return Value: The function value is the final status from the operation. --*/ { PDEVICE_EXTENSION_HUB rootHubDevExt; KEVENT event; POWER_STATE powerState; NTSTATUS ntStatus; rootHubDevExt = USBH_GetRootHubDevExt(DeviceExtensionHub); // Skip powering up the hub if the system is not at S0. if (rootHubDevExt->CurrentSystemPowerState != PowerSystemWorking) { USBH_KdPrint((1,"'HubSetD0, skip power up hub %x because system not at S0\n", DeviceExtensionHub)); return STATUS_INVALID_DEVICE_STATE; } USBH_KdPrint((1,"'HubSetD0, power up hub %x\n", DeviceExtensionHub)); LOGENTRY(LOG_PNP, "H!D0", DeviceExtensionHub, DeviceExtensionHub->CurrentPowerState, rootHubDevExt->CurrentSystemPowerState); // If the parent hub is currently in the process of idling out, // wait until that is done. if (DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_IDLE_IRP) { USBH_KdPrint((2,"'Wait for single object\n")); ntStatus = KeWaitForSingleObject(&DeviceExtensionHub->SubmitIdleEvent, Suspended, KernelMode, FALSE, NULL); USBH_KdPrint((2,"'Wait for single object, returned %x\n", ntStatus)); } // Now, send the actual power up request. KeInitializeEvent(&event, NotificationEvent, FALSE); powerState.DeviceState = PowerDeviceD0; // Power up the hub. ntStatus = PoRequestPowerIrp(DeviceExtensionHub->PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, USBH_HubSetD0Completion, &event, NULL); USBH_ASSERT(ntStatus == STATUS_PENDING); if (ntStatus == STATUS_PENDING) { USBH_KdPrint((2,"'Wait for single object\n")); ntStatus = KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL); USBH_KdPrint((2,"'Wait for single object, returned %x\n", ntStatus)); } return ntStatus; } NTSTATUS USBH_SetPowerD0( IN PIRP Irp, IN PDEVICE_EXTENSION_PORT DeviceExtensionPort ) /*++ Routine Description: Put the PDO in D0 Arguments: DeviceExtensionPort - port PDO deviceExtension Irp - Power Irp. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; PPORT_DATA portData; PORT_STATE state; PAGED_CODE(); irpStack = IoGetCurrentIrpStackLocation(Irp); deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; portNumber = DeviceExtensionPort->PortNumber; portData = &deviceExtensionHub->PortData[portNumber - 1]; USBH_KdPrint((2,"'PdoSetPower D0\n")); LOGENTRY(LOG_PNP, "P>D0", deviceExtensionHub, DeviceExtensionPort, DeviceExtensionPort->DeviceState); if (DeviceExtensionPort->DeviceState == PowerDeviceD3) { // // device was in D3, port may be off or suspended // we will need to reset the port state in any case // // get port state ntStatus = USBH_SyncGetPortStatus(deviceExtensionHub, portNumber, (PUCHAR) &state, sizeof(state)); // refresh our internal port state. portData->PortState = state; LOGENTRY(LOG_PNP, "PD0s", deviceExtensionHub, *((PULONG) &state), ntStatus); if (NT_SUCCESS(ntStatus)) { // port state should be suspended or OFF // if the hub was powered off then the port // state will be powered but disabled if ((state.PortStatus & PORT_STATUS_SUSPEND)) { // // resume the port if it was suspended // ntStatus = USBH_SyncResumePort(deviceExtensionHub, portNumber); } else if (!(state.PortStatus & PORT_STATUS_POWER)) { // // probably some kind of selective OFF by the device // driver -- we just need to power on the port // // this requires a hub with individual port power // switching. // ntStatus = USBH_SyncPowerOnPort(deviceExtensionHub, portNumber, TRUE); } } else { // the hub driver will notify thru WMI USBH_KdPrint((0, "'Hub failed after power change from D3\n")); // USBH_ASSERT(FALSE); } // // if port power switched on this is just like plugging // in the device for the first time. // NOTE: // ** the driver should know that the device needs to be // re-initialized since it allowed it's PDO to go in to // the D3 state. // // We always call restore device even though we don't need // to if the port was only suspended, we do this so that // drivers don't relay on the suspend behavior by mistake. // if (NT_SUCCESS(ntStatus)) { // // if we still have a device connected attempt to // restore it. // // // Note: we check here to see if the device object still // exists in case a change is asserted during the resume. // // Note also that we now ignore the connect status bit because // some machines (e.g. Compaq Armada 7800) are slow to power // up the ports on the resume and thus port status can show // no device connected when in fact one is. It shouldn't // hurt to try to restore the device if it had been removed // during the suspend/hibernate. In fact, this code handled the // case where the device had been swapped for another, so this // is really no different. if (portData->DeviceObject) { // // if this fails the device must have changed // during power off, in that case we succeed the // power on. // // it will be tossed on the next enumeration // and relaced with this new device // if (USBH_RestoreDevice(DeviceExtensionPort, TRUE) != STATUS_SUCCESS) { PDEVICE_OBJECT pdo = portData->DeviceObject; LOGENTRY(LOG_PNP, "PD0!", DeviceExtensionPort, 0, pdo); USBH_KdPrint((1,"'Device appears to have been swapped during power off\n")); USBH_KdPrint((1,"'Marking PDO %x for removal\n", portData->DeviceObject)); // leave ref to hub since device data wll need to be // deleted on remove. portData->DeviceObject = NULL; portData->ConnectionStatus = NoDeviceConnected; // track the Pdo so we know to remove it after we tell PnP it // is gone // device should be present if we do this USBH_ASSERT(PDO_EXT(pdo)->PnPFlags & PDO_PNPFLAG_DEVICE_PRESENT); InsertTailList(&deviceExtensionHub->DeletePdoList, &PDO_EXT(pdo)->DeletePdoLink); } } DeviceExtensionPort->DeviceState = irpStack->Parameters.Power.State.DeviceState; } } else if (DeviceExtensionPort->DeviceState == PowerDeviceD2 || DeviceExtensionPort->DeviceState == PowerDeviceD1) { // get port state ntStatus = USBH_SyncGetPortStatus(deviceExtensionHub, portNumber, (PUCHAR) &state, sizeof(state)); // // if we got an error assume then the hub is hosed // just set our flag and bail // if (NT_SUCCESS(ntStatus)) { // see if suspeneded (according to spec). Otherwise only // try to resume if the port is really suspended. // if (state.PortStatus & PORT_STATUS_OVER_CURRENT) { // // overcurrent condition indicates this port // (and hub) are hosed ntStatus = STATUS_UNSUCCESSFUL; } else if (state.PortStatus & PORT_STATUS_SUSPEND) { ntStatus = USBH_SyncResumePort(deviceExtensionHub, portNumber); } else { // // Most OHCI controllers enable all the ports after a usb // resume on any port (in violation of the USB spec), in this // case we should detect that the port is no longer in suspend // and not try to resume it. // // Also, if the device where removed while suspended or the HC // lost power we should end up here. // ntStatus = STATUS_SUCCESS; } } else { USBH_KdPrint((0, "'Hub failed after power change from D2/D1\n")); LOGENTRY(LOG_PNP, "d0f!", deviceExtensionHub, 0, 0); // USBH_ASSERT(FALSE); } // // port is now in D0 // DeviceExtensionPort->DeviceState = irpStack->Parameters.Power.State.DeviceState; USBH_CompletePortIdleNotification(DeviceExtensionPort); if (NT_SUCCESS(ntStatus)) { if (DeviceExtensionPort->PortPdoFlags & PORTPDO_NEED_CLEAR_REMOTE_WAKEUP) { NTSTATUS status; // // disable remote wakeup // status = USBH_SyncFeatureRequest(DeviceExtensionPort->PortPhysicalDeviceObject, USB_FEATURE_REMOTE_WAKEUP, 0, TO_USB_DEVICE, TRUE); DeviceExtensionPort->PortPdoFlags &= ~PORTPDO_NEED_CLEAR_REMOTE_WAKEUP; } } } if (!NT_SUCCESS(ntStatus)) { USBH_KdPrint((1,"'Set D0 Failure, status = %x\n", ntStatus)); LOGENTRY(LOG_PNP, "d0!!", deviceExtensionHub, 0, ntStatus); // we return success to PNP, we will let // the driver handle the fact that the // device has lost its brains // // NB: This can result in a redundant suspend request for this port // later on. (Since if we fail here port will remain suspended, // but our state will indicate that we are in D0.) ntStatus = STATUS_SUCCESS; } DeviceExtensionPort->DeviceState = irpStack->Parameters.Power.State.DeviceState; USBH_KdPrint((1, "'Setting HU pdo(%x) to D0, status = %x complt IRP (%x)\n", DeviceExtensionPort->PortPhysicalDeviceObject, ntStatus, Irp)); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); return ntStatus; } VOID USBH_IdleCancelPowerHubWorker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to power up a hub on completion of an Idle request * for the hub. * * * Arguments: * * Return: * * -- */ { PUSBH_PORT_IDLE_POWER_WORK_ITEM workItemIdlePower; PIRP irp; PAGED_CODE(); workItemIdlePower = Context; USBH_HubSetD0(workItemIdlePower->DeviceExtensionHub); irp = workItemIdlePower->Irp; irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(irp, IO_NO_INCREMENT); USBH_DEC_PENDING_IO_COUNT(workItemIdlePower->DeviceExtensionHub); UsbhExFreePool(workItemIdlePower); } VOID USBH_PortIdleNotificationCancelRoutine( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: Arguments: DeviceObject - Irp - Power Irp. Return Value: --*/ { PUSBH_PORT_IDLE_POWER_WORK_ITEM workItemIdlePower; PDEVICE_EXTENSION_PORT deviceExtensionPort; PDEVICE_EXTENSION_HUB deviceExtensionHub; PIRP irpToCancel = NULL; USBH_KdPrint((1,"'Idle notification IRP %x cancelled\n", Irp)); deviceExtensionPort = DeviceObject->DeviceExtension; USBH_ASSERT(deviceExtensionPort->IdleNotificationIrp == NULL || deviceExtensionPort->IdleNotificationIrp == Irp); deviceExtensionPort->IdleNotificationIrp = NULL; deviceExtensionPort->PortPdoFlags &= ~PORTPDO_IDLE_NOTIFIED; deviceExtensionHub = deviceExtensionPort->DeviceExtensionHub; if (deviceExtensionHub && deviceExtensionHub->HubFlags & HUBFLAG_PENDING_IDLE_IRP) { irpToCancel = deviceExtensionHub->PendingIdleIrp; deviceExtensionHub->PendingIdleIrp = NULL; } else { ASSERT(!deviceExtensionHub->PendingIdleIrp); } IoReleaseCancelSpinLock(Irp->CancelIrql); // Cancel the Idle request to the hub if there is one. if (irpToCancel) { USBH_HubCancelIdleIrp(deviceExtensionHub, irpToCancel); } // Also, power up the hub here before we complete this Idle IRP. // // (HID will start to send requests immediately upon its completion, // which may be before the hub's Idle IRP cancel routine is called // which powers up the hub.) if (deviceExtensionHub->CurrentPowerState != PowerDeviceD0) { // Since we are at DPC we must use a work item to power up the // hub synchronously, because that function yields and we can't // yield at DPC level. workItemIdlePower = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_PORT_IDLE_POWER_WORK_ITEM)); if (workItemIdlePower) { workItemIdlePower->DeviceExtensionHub = deviceExtensionHub; workItemIdlePower->Irp = Irp; ExInitializeWorkItem(&workItemIdlePower->WorkQueueItem, USBH_IdleCancelPowerHubWorker, workItemIdlePower); LOGENTRY(LOG_PNP, "icIT", deviceExtensionHub, &workItemIdlePower->WorkQueueItem, 0); USBH_INC_PENDING_IO_COUNT(deviceExtensionHub); ExQueueWorkItem(&workItemIdlePower->WorkQueueItem, DelayedWorkQueue); // The WorkItem is freed by USBH_IdleCancelPowerHubWorker() // Don't try to access the WorkItem after it is queued. } } else { Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); } } VOID USBH_CompletePortIdleNotification( IN PDEVICE_EXTENSION_PORT DeviceExtensionPort ) { NTSTATUS status; KIRQL irql; PIRP irp = NULL; PDRIVER_CANCEL oldCancelRoutine; IoAcquireCancelSpinLock(&irql); if (DeviceExtensionPort->IdleNotificationIrp) { irp = DeviceExtensionPort->IdleNotificationIrp; oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine) { USBH_ASSERT(oldCancelRoutine == USBH_PortIdleNotificationCancelRoutine); DeviceExtensionPort->IdleNotificationIrp = NULL; DeviceExtensionPort->PortPdoFlags &= ~PORTPDO_IDLE_NOTIFIED; } #if DBG else { USBH_ASSERT(irp->Cancel); } #endif } IoReleaseCancelSpinLock(irql); if (irp) { USBH_KdPrint((1,"'Completing idle request IRP %x\n", irp)); irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(irp, IO_NO_INCREMENT); } } NTSTATUS USBH_SetPowerD1orD2( IN PIRP Irp, IN PDEVICE_EXTENSION_PORT DeviceExtensionPort ) /*++ Routine Description: Put the PDO in D1/D2 ie suspend Arguments: DeviceExtensionPort - port PDO deviceExtension Irp - Worker Irp. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus = STATUS_SUCCESS; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; PAGED_CODE(); irpStack = IoGetCurrentIrpStackLocation(Irp); deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; portNumber = DeviceExtensionPort->PortNumber; USBH_KdPrint((2,"'PdoSetPower D1/D2\n")); if (DeviceExtensionPort->DeviceState == PowerDeviceD1 || DeviceExtensionPort->DeviceState == PowerDeviceD2) { return STATUS_SUCCESS; } // // Enable the device for remote wakeup if necessary // if (DeviceExtensionPort->PortPdoFlags & PORTPDO_REMOTE_WAKEUP_ENABLED) { NTSTATUS status; status = USBH_SyncFeatureRequest(DeviceExtensionPort->PortPhysicalDeviceObject, USB_FEATURE_REMOTE_WAKEUP, 0, TO_USB_DEVICE, FALSE); DeviceExtensionPort->PortPdoFlags |= PORTPDO_NEED_CLEAR_REMOTE_WAKEUP; #if DBG // With the new Selective Suspend support, people are complaining // about this noise. Let's display it only if debug trace level // is 1 or higher. if (USBH_Debug_Trace_Level > 0) { UsbhWarning(DeviceExtensionPort, "Device is Enabled for REMOTE WAKEUP\n", FALSE); } #endif // what do we do about an error here? // perhaps signal the waitwake irp?? } ntStatus = USBH_SyncSuspendPort(deviceExtensionHub, portNumber); // // keep track of what OS thinks is the current power state of the // the device on this port. // DeviceExtensionPort->DeviceState = irpStack->Parameters.Power.State.DeviceState; DeviceExtensionPort->PortPdoFlags |= PORTPDO_USB_SUSPEND; USBH_KdPrint((2,"'DeviceExtensionPort->DeviceState = %x\n", DeviceExtensionPort->DeviceState)); if (!NT_SUCCESS(ntStatus)) { USBH_KdPrint((1,"'Set D1/D2 Failure, status = %x\n", ntStatus)); // don't pass an error to PnP ntStatus = STATUS_SUCCESS; } USBH_KdPrint((1, "'Setting HU pdo(%x) to D%d, status = %x complt\n", DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.DeviceState - 1, ntStatus)); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); return ntStatus; } NTSTATUS USBH_PdoQueryPower( IN PDEVICE_EXTENSION_PORT DeviceExtensionPort, IN PIRP Irp ) /* ++ * * Description: * * Handles a power irp to a hub PDO * * Arguments: * * Return: * * NTSTATUS * * -- */ { NTSTATUS ntStatus; PDEVICE_OBJECT deviceObject; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; PPORT_DATA portData; POWER_STATE powerState; PAGED_CODE(); irpStack = IoGetCurrentIrpStackLocation(Irp); deviceObject = DeviceExtensionPort->PortPhysicalDeviceObject; deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; USBH_ASSERT( DeviceExtensionPort->PortNumber < 1000); portNumber = DeviceExtensionPort->PortNumber; portData = &deviceExtensionHub->PortData[portNumber - 1]; USBH_KdPrint((2,"'USBH_PdoQueryPower pdo(%x)\n", deviceObject)); switch (irpStack->Parameters.Power.Type) { case SystemPowerState: { // // We are currently faced with the decision to fail or allow the // transition to the given S power state. In order to make an // informed decision, we must first calculate the maximum amount // of D power allowed in the given S state, and then see if this // conflicts with a pending Wait Wake IRP. // // // The maximum amount of D power allowed in this S state. // powerState.DeviceState = deviceExtensionHub->DeviceState[irpStack->Parameters.Power.State.SystemState]; // // These tables should have already been fixed up by the root hub // (usbd.sys) to not contain an entry of unspecified. // ASSERT (PowerDeviceUnspecified != powerState.DeviceState); // // The presence of a pending wait wake irp together with a D state that // will not support waking of the machine means we should fail this // query. // // However, if we are going into Hibernate (or power off) then we // should not fail this query. // if (powerState.DeviceState == PowerDeviceD3 && DeviceExtensionPort->WaitWakeIrp && irpStack->Parameters.Power.State.SystemState < PowerSystemHibernate) { ntStatus = STATUS_UNSUCCESSFUL; USBH_KdPrint( (1, "'IRP_MJ_POWER HU pdo(%x) MN_QUERY_POWER Failing Query\n", deviceObject)); } else { ntStatus = STATUS_SUCCESS; } LOGENTRY(LOG_PNP, "QPWR", DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.SystemState, powerState.DeviceState); USBH_KdPrint( (1, "'IRP_MJ_POWER HU pdo(%x) MN_QUERY_POWER(S%x -> D%x), complt %x\n", DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.SystemState - 1, powerState.DeviceState - 1, ntStatus)); #if DBG if (!NT_SUCCESS(ntStatus)) { LOGENTRY(LOG_PNP, "QPW!", deviceExtensionHub, DeviceExtensionPort->WaitWakeIrp, ntStatus); } #endif USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } break; case DevicePowerState: // Return success on this one or NDIS will choke on the suspend. ntStatus = STATUS_SUCCESS; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); break; default: TEST_TRAP(); ntStatus = STATUS_INVALID_PARAMETER; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } /* power type */ return ntStatus; } NTSTATUS USBH_PdoSetPower( IN PDEVICE_EXTENSION_PORT DeviceExtensionPort, IN PIRP Irp ) /* ++ * * Description: * * Handles a power irp to a hub PDO * * Arguments: * * Return: * * NTSTATUS * * -- */ { NTSTATUS ntStatus; PDEVICE_OBJECT deviceObject; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; PPORT_DATA portData; PAGED_CODE(); irpStack = IoGetCurrentIrpStackLocation(Irp); deviceObject = DeviceExtensionPort->PortPhysicalDeviceObject; deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; USBH_ASSERT( DeviceExtensionPort->PortNumber < 1000); portNumber = DeviceExtensionPort->PortNumber; portData = &deviceExtensionHub->PortData[portNumber - 1]; USBH_KdPrint((2,"'USBH_PdoSetPower pdo(%x)\n", deviceObject)); switch (irpStack->Parameters.Power.Type) { case SystemPowerState: { // // see if the current state of this pdo is valid for the // system state , if is not then we will need to set the // pdo to a valid D state. // ntStatus = STATUS_SUCCESS; USBH_KdPrint( (1, "'IRP_MJ_POWER HU pdo(%x) MN_SET_POWER(SystemPowerState S%x), complt\n", DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.DeviceState - 1, ntStatus)); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } break; case DevicePowerState: USBH_KdPrint( (1, "'IRP_MJ_POWER HU pdo(%x) MN_SET_POWER(DevicePowerState D%x)\n", DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.DeviceState - 1)); LOGENTRY(LOG_PNP, "P>Dx", deviceExtensionHub, DeviceExtensionPort->PortPhysicalDeviceObject, irpStack->Parameters.Power.State.DeviceState); // If we are already in the requested power state, // just complete the request. if (DeviceExtensionPort->DeviceState == irpStack->Parameters.Power.State.DeviceState) { // If we are skipping this set power request and it is a SetD0 // request, assert that the parent hub is in D0. USBH_ASSERT(DeviceExtensionPort->DeviceState != PowerDeviceD0 || deviceExtensionHub->CurrentPowerState == PowerDeviceD0); ntStatus = STATUS_SUCCESS; goto PdoSetPowerCompleteIrp; } // USBH_ASSERT(deviceExtensionHub->CurrentPowerState == PowerDeviceD0); switch (irpStack->Parameters.Power.State.DeviceState) { case PowerDeviceD0: ntStatus = USBH_SetPowerD0(Irp, DeviceExtensionPort); break; case PowerDeviceD1: case PowerDeviceD2: ntStatus = USBH_SetPowerD1orD2(Irp, DeviceExtensionPort); break; case PowerDeviceD3: // // In the case of D3 we need to complete any pending WaitWake // Irps with the status code STATUS_POWER_STATE_INVALID. // This is done in USBH_SetPowerD3. // ntStatus = USBH_SetPowerD3(Irp, DeviceExtensionPort); break; default: USBH_KdTrap(("Bad Power State\n")); ntStatus = STATUS_INVALID_PARAMETER; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } break; default: TEST_TRAP(); ntStatus = STATUS_INVALID_PARAMETER; PdoSetPowerCompleteIrp: USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } /* power type */ return ntStatus; } VOID USBH_WaitWakeCancel( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: Arguments: Return Value: NT status code. --*/ { PDEVICE_EXTENSION_PORT deviceExtensionPort; PDEVICE_EXTENSION_HUB deviceExtensionHub; NTSTATUS ntStatus = STATUS_CANCELLED; LONG pendingPortWWs; PIRP hubWaitWake = NULL; USBH_KdPrint((1,"'WaitWake Irp %x for PDO cancelled\n", Irp)); USBH_ASSERT(DeviceObject); deviceExtensionPort = (PDEVICE_EXTENSION_PORT) Irp->IoStatus.Information; deviceExtensionHub = deviceExtensionPort->DeviceExtensionHub; LOGENTRY(LOG_PNP, "WWca", Irp, deviceExtensionPort, deviceExtensionHub); if (Irp != deviceExtensionPort->WaitWakeIrp) { // // Nothing to do // This Irp has already been taken care of. // We are in the process of completing this IRP in // USBH_HubCompletePortWakeIrps. // TEST_TRAP(); IoReleaseCancelSpinLock(Irp->CancelIrql); } else { deviceExtensionPort->WaitWakeIrp = NULL; deviceExtensionPort->PortPdoFlags &= ~PORTPDO_REMOTE_WAKEUP_ENABLED; IoSetCancelRoutine(Irp, NULL); pendingPortWWs = InterlockedDecrement(&deviceExtensionHub->NumberPortWakeIrps); if (0 == pendingPortWWs && deviceExtensionHub->PendingWakeIrp) { // Set PendingWakeIrp to NULL since we cancel it below. hubWaitWake = deviceExtensionHub->PendingWakeIrp; deviceExtensionHub->PendingWakeIrp = NULL; } IoReleaseCancelSpinLock(Irp->CancelIrql); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); // // If there are no more outstanding WW irps, we need to cancel the WW // to the hub. // if (hubWaitWake) { USBH_HubCancelWakeIrp(deviceExtensionHub, hubWaitWake); } // else { // This assert is no longer valid as I now clear the PendingWakeIrp // pointer for the hub in USBH_FdoWaitWakeIrpCompletion, instead // of waiting to do it here when NumberPortWakeIrps reaches zero. // So it is completely normal to arrive here with no port wake // IRP's and a NULL PendingWakeIrp for the hub. // ASSERT (0 < pendingPortWWs); // } } } NTSTATUS USBH_PdoWaitWake( IN PDEVICE_EXTENSION_PORT DeviceExtensionPort, IN PIRP Irp ) /* ++ * * Description: * * Arguments: * * Return: * * NTSTATUS * * -- */ { NTSTATUS ntStatus = STATUS_SUCCESS; PDEVICE_OBJECT deviceObject; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; USHORT portNumber; PPORT_DATA portData; KIRQL irql; PDRIVER_CANCEL oldCancel; LONG pendingPortWWs = 0; irpStack = IoGetCurrentIrpStackLocation(Irp); deviceObject = DeviceExtensionPort->PortPhysicalDeviceObject; deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; USBH_ASSERT( DeviceExtensionPort->PortNumber < 1000); portNumber = (USHORT) DeviceExtensionPort->PortNumber; portData = &deviceExtensionHub->PortData[portNumber - 1]; USBH_KdPrint((2,"'PnP WaitWake Irp passed to PDO %x\n", deviceObject)); LOGENTRY(LOG_PNP, "PWW_", deviceObject, DeviceExtensionPort, deviceExtensionHub); if (DeviceExtensionPort->DeviceState != PowerDeviceD0 || deviceExtensionHub->HubFlags & HUBFLAG_DEVICE_STOPPING) { LOGENTRY(LOG_PNP, "!WWh", DeviceExtensionPort, deviceExtensionHub, 0); UsbhWarning(NULL, "Client driver should not be submitting WW IRPs at this time.\n", TRUE); ntStatus = STATUS_INVALID_DEVICE_STATE; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); return ntStatus; } // // First verify that there is not already a WaitWake Irp pending for // this PDO. // // // make sure that this device can support remote wakeup. // // NOTE: that we treat all hubs as capable of remote // wakeup regardless of what the device reports. The reason // is that all hubs must propagate resume signalling regardless // of their abilty to generate resume signalling on a // plug-in/out event. // #if DBG if (UsbhPnpTest & PNP_TEST_FAIL_WAKE_REQUEST) { DeviceExtensionPort->PortPdoFlags &= ~PORTPDO_REMOTE_WAKEUP_SUPPORTED; } #endif if (DeviceExtensionPort->PortPdoFlags & PORTPDO_REMOTE_WAKEUP_SUPPORTED) { IoAcquireCancelSpinLock(&irql); if (DeviceExtensionPort->WaitWakeIrp != NULL) { LOGENTRY(LOG_PNP, "PWWx", deviceObject, DeviceExtensionPort, DeviceExtensionPort->WaitWakeIrp); ntStatus = STATUS_DEVICE_BUSY; IoReleaseCancelSpinLock(irql); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } else { // set a cancel routine oldCancel = IoSetCancelRoutine(Irp, USBH_WaitWakeCancel); USBH_ASSERT (NULL == oldCancel); if (Irp->Cancel) { oldCancel = IoSetCancelRoutine(Irp, NULL); if (oldCancel) { // // Cancel routine hasn't fired. // ASSERT(oldCancel == USBH_WaitWakeCancel); ntStatus = STATUS_CANCELLED; IoReleaseCancelSpinLock(irql); USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } else { // // Cancel routine WAS called // IoMarkIrpPending(Irp); ntStatus = Irp->IoStatus.Status = STATUS_PENDING; IoReleaseCancelSpinLock(irql); } } else { USBH_KdPrint( (1, "'enabling remote wakeup for USB device PDO (%x)\n", DeviceExtensionPort->PortPhysicalDeviceObject)); // flag this device as "enabled for wakeup" DeviceExtensionPort->WaitWakeIrp = Irp; DeviceExtensionPort->PortPdoFlags |= PORTPDO_REMOTE_WAKEUP_ENABLED; Irp->IoStatus.Information = (ULONG_PTR) DeviceExtensionPort; pendingPortWWs = InterlockedIncrement(&deviceExtensionHub->NumberPortWakeIrps); IoMarkIrpPending(Irp); LOGENTRY(LOG_PNP, "PWW+", DeviceExtensionPort, Irp, pendingPortWWs); IoReleaseCancelSpinLock(irql); ntStatus = STATUS_PENDING; } } // // Now we must enable the hub for wakeup. // // We may already have a WW IRP pending if this hub were previously // selective suspended, but we had to power it back on (USBH_HubSetD0) // for a PnP request. Don't post a new WW IRP if there is already // one pending. // if (ntStatus == STATUS_PENDING && 1 == pendingPortWWs && !(deviceExtensionHub->HubFlags & HUBFLAG_PENDING_WAKE_IRP)) { USBH_FdoSubmitWaitWakeIrp(deviceExtensionHub); } } else { ntStatus = STATUS_NOT_SUPPORTED; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } return ntStatus; } VOID USBH_HubAsyncPowerWorker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to handle a hub ESD failure. * * * Arguments: * * Return: * * -- */ { PUSBH_HUB_ASYNC_POWER_WORK_ITEM context; NTSTATUS ntStatus; PAGED_CODE(); context = Context; if (context->Irp->PendingReturned) { IoMarkIrpPending(context->Irp); } switch (context->MinorFunction) { case IRP_MN_SET_POWER: ntStatus = USBH_PdoSetPower(context->DeviceExtensionPort, context->Irp); break; case IRP_MN_QUERY_POWER: ntStatus = USBH_PdoQueryPower(context->DeviceExtensionPort, context->Irp); break; default: // Should never get here. USBH_ASSERT(FALSE); } UsbhExFreePool(context); } NTSTATUS USBH_HubAsyncPowerSetD0Completion( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus ) /*++ Routine Description: Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { PUSBH_HUB_ASYNC_POWER_WORK_ITEM context; NTSTATUS ntStatus, status; context = Context; ntStatus = IoStatus->Status; // We schedule the work item regardless of whether the hub power up // request was successful or not. ExInitializeWorkItem(&context->WorkQueueItem, USBH_HubAsyncPowerWorker, context); LOGENTRY(LOG_PNP, "HAPW", context->DeviceExtensionPort, &context->WorkQueueItem, 0); // critical saves time on resume ExQueueWorkItem(&context->WorkQueueItem, CriticalWorkQueue); // The WorkItem is freed by USBH_HubAsyncPowerWorker() // Don't try to access the WorkItem after it is queued. return ntStatus; } NTSTATUS USBH_PdoPower( IN PDEVICE_EXTENSION_PORT DeviceExtensionPort, IN PIRP Irp, IN UCHAR MinorFunction ) /* ++ * * Description: * * This function responds to IoControl Power for the PDO. This function is * synchronous. * * Arguments: * * DeviceExtensionPort - the PDO extension Irp - the request packet * uchMinorFunction - the minor function of the PnP Power request. * * Return: * * NTSTATUS * * -- */ { NTSTATUS ntStatus; PDEVICE_OBJECT deviceObject; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub; POWER_STATE powerState; PUSBH_HUB_ASYNC_POWER_WORK_ITEM context; PAGED_CODE(); deviceExtensionHub = DeviceExtensionPort->DeviceExtensionHub; irpStack = IoGetCurrentIrpStackLocation(Irp); deviceObject = DeviceExtensionPort->PortPhysicalDeviceObject; USBH_KdPrint((2,"'USBH_PdoPower pdo(%x)\n", deviceObject)); // special case device removed if (deviceExtensionHub == NULL) { // if there is no backpointer to the parent hub then there // is a delete/remove comming. just complete this power // request with success USBH_KdPrint((1,"'complete power on orphan Pdo %x\n", deviceObject)); if (MinorFunction == IRP_MN_SET_POWER || MinorFunction == IRP_MN_QUERY_POWER) { Irp->IoStatus.Status = ntStatus = STATUS_SUCCESS; PoStartNextPowerIrp(Irp); } else { Irp->IoStatus.Status = ntStatus = STATUS_NOT_SUPPORTED; } PoStartNextPowerIrp(Irp); IoCompleteRequest(Irp, IO_NO_INCREMENT); return ntStatus; } USBH_ASSERT(deviceExtensionHub); // specail case device not in D0 // one more item pending in the hub USBH_INC_PENDING_IO_COUNT(deviceExtensionHub); // If the hub has been selectively suspended, then we need to power it up // to service QUERY or SET POWER requests. However, we can't block on // this power IRP waiting for the parent hub to power up, so we need to // power up the parent hub asynchronously and handle this IRP after the // hub power up request has completed. Major PITA. if (deviceExtensionHub->CurrentPowerState != PowerDeviceD0 && (MinorFunction == IRP_MN_SET_POWER || MinorFunction == IRP_MN_QUERY_POWER)) { // Allocate buffer for context. context = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_HUB_ASYNC_POWER_WORK_ITEM)); if (context) { context->DeviceExtensionPort = DeviceExtensionPort; context->Irp = Irp; context->MinorFunction = MinorFunction; // We'll complete this IRP in the completion routine for the hub's // Set D0 IRP. IoMarkIrpPending(Irp); powerState.DeviceState = PowerDeviceD0; // Power up the hub. ntStatus = PoRequestPowerIrp(deviceExtensionHub->PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, USBH_HubAsyncPowerSetD0Completion, context, NULL); // We need to return STATUS_PENDING here because we marked the // IRP pending above with IoMarkIrpPending. USBH_ASSERT(ntStatus == STATUS_PENDING); // In the case where an allocation failed, PoRequestPowerIrp can // return a status code other than STATUS_PENDING. In this case, // we need to complete the IRP passed to us, but we still need // to return STATUS_PENDING from this routine. if (ntStatus != STATUS_PENDING) { USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } ntStatus = STATUS_PENDING; } else { ntStatus = STATUS_INSUFFICIENT_RESOURCES; USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } } else switch (MinorFunction) { case IRP_MN_SET_POWER: ntStatus = USBH_PdoSetPower(DeviceExtensionPort, Irp); break; case IRP_MN_WAIT_WAKE: ntStatus = USBH_PdoWaitWake(DeviceExtensionPort, Irp); USBH_KdPrint((1, "'IRP_MN_WAIT_WAKE pdo(%x), status = 0x%x\n", DeviceExtensionPort->PortPhysicalDeviceObject, ntStatus)); break; case IRP_MN_QUERY_POWER: ntStatus = USBH_PdoQueryPower(DeviceExtensionPort, Irp); break; default: ntStatus = Irp->IoStatus.Status; USBH_KdPrint((1, "'IRP_MN_[%d](%x), status = 0x%x (not handled)\n", MinorFunction, DeviceExtensionPort->PortPhysicalDeviceObject, ntStatus)); USBH_KdBreak(("PdoPower unknown\n")); // // return the original status passed to us // USBH_CompletePowerIrp(deviceExtensionHub, Irp, ntStatus); } USBH_KdPrint((2,"'USBH_PdoPower pdo exit %x\n", ntStatus)); return ntStatus; } VOID USBH_SetPowerD0Worker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to handle a Set Power D0 IRP for the hub. * * * Arguments: * * Return: * * -- */ { PUSBH_SET_POWER_D0_WORK_ITEM workItemSetPowerD0; PDEVICE_EXTENSION_HUB deviceExtensionHub; PIRP irp; PPORT_DATA portData; ULONG p, numberOfPorts; NTSTATUS ntStatus = STATUS_SUCCESS; workItemSetPowerD0 = Context; deviceExtensionHub = workItemSetPowerD0->DeviceExtensionHub; irp = workItemSetPowerD0->Irp; USBH_KdPrint((2,"'Hub Set Power D0 work item\n")); LOGENTRY(LOG_PNP, "HD0W", deviceExtensionHub, irp, 0); // restore the hub from OFF // the device has lost its brains, we need to go thru the // init process again // our ports will be indicating status changes at this // point. We need to flush out any change indications // before we re-enable the hub // first clear out our port status info portData = deviceExtensionHub->PortData; if (portData && deviceExtensionHub->HubDescriptor) { numberOfPorts = deviceExtensionHub->HubDescriptor->bNumberOfPorts; // first clear out our port status info for (p = 1; p <= numberOfPorts; p++, portData++) { portData->PortState.PortChange = 0; portData->PortState.PortStatus = 0; } portData = deviceExtensionHub->PortData; // power up the hub ntStatus = USBH_SyncPowerOnPorts(deviceExtensionHub); // Probably need to enable this code for Mike Mangum's bug. // UsbhWait(500); // Allow USB storage devices some time to power up. // flush out any change indications if (NT_SUCCESS(ntStatus)) { for (p = 1; p <= numberOfPorts; p++, portData++) { if (portData->DeviceObject) { ntStatus = USBH_FlushPortChange(deviceExtensionHub, portData->DeviceObject->DeviceExtension); if (NT_ERROR(ntStatus)) { LOGENTRY(LOG_PNP, "flsX", deviceExtensionHub, p, portData->DeviceObject); USBH_KdPrint((1,"'USBH_FlushPortChange failed!\n")); } } } } // Since we just flushed all port changes we now don't // know if there were any real port changes (e.g. a // device was unplugged). We must call // IoInvalidateDeviceRelations to trigger a QBR // so that we can see if the devices are still there. USBH_IoInvalidateDeviceRelations(deviceExtensionHub->PhysicalDeviceObject, BusRelations); } if (!(deviceExtensionHub->HubFlags & HUBFLAG_HUB_STOPPED)) { USBH_SubmitInterruptTransfer(deviceExtensionHub); } // Tell ACPI that we are ready for another power IRP and complete // the IRP. irp->IoStatus.Status = ntStatus; PoStartNextPowerIrp(irp); IoCompleteRequest(irp, IO_NO_INCREMENT); USBH_DEC_PENDING_IO_COUNT(deviceExtensionHub); UsbhExFreePool(workItemSetPowerD0); } NTSTATUS USBH_PowerIrpCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This routine is called when the port driver completes an IRP. Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus; PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION_HUB deviceExtensionHub = Context; DEVICE_POWER_STATE oldPowerState; PUSBH_SET_POWER_D0_WORK_ITEM workItemSetPowerD0; irpStack = IoGetCurrentIrpStackLocation(Irp); ntStatus = Irp->IoStatus.Status; USBH_ASSERT(irpStack->Parameters.Power.Type == DevicePowerState); LOGENTRY(LOG_PNP, "PwrC", deviceExtensionHub, Irp, irpStack->Parameters.Power.State.DeviceState); if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } if (NT_SUCCESS(ntStatus)) { switch (irpStack->Parameters.Power.State.DeviceState) { case PowerDeviceD0: oldPowerState = deviceExtensionHub->CurrentPowerState; deviceExtensionHub->CurrentPowerState = irpStack->Parameters.Power.State.DeviceState; deviceExtensionHub->HubFlags &= ~HUBFLAG_SET_D0_PENDING; if ((deviceExtensionHub->HubFlags & HUBFLAG_HIBER) && oldPowerState != PowerDeviceD3) { ULONG p, numberOfPorts; PPORT_DATA portData; PDEVICE_EXTENSION_PORT deviceExtensionPort; // we are going to d0 from hibernate, we may // have been in D2 but we want to always go // thru the D3->D0 codepath since the bus was reset. oldPowerState = PowerDeviceD3; // modify children numberOfPorts = deviceExtensionHub->HubDescriptor->bNumberOfPorts; portData = deviceExtensionHub->PortData; for (p = 1; p <= numberOfPorts; p++, portData++) { if (portData->DeviceObject) { deviceExtensionPort = portData->DeviceObject->DeviceExtension; deviceExtensionPort->DeviceState = PowerDeviceD3; deviceExtensionPort->PortPdoFlags |= PORTPDO_NEED_RESET; } } } deviceExtensionHub->HubFlags &= ~HUBFLAG_HIBER; if (oldPowerState == PowerDeviceD3) { // // Schedule a work item to process this. // workItemSetPowerD0 = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_SET_POWER_D0_WORK_ITEM)); if (workItemSetPowerD0) { workItemSetPowerD0->DeviceExtensionHub = deviceExtensionHub; workItemSetPowerD0->Irp = Irp; ExInitializeWorkItem(&workItemSetPowerD0->WorkQueueItem, USBH_SetPowerD0Worker, workItemSetPowerD0); LOGENTRY(LOG_PNP, "HD0Q", deviceExtensionHub, &workItemSetPowerD0->WorkQueueItem, 0); USBH_INC_PENDING_IO_COUNT(deviceExtensionHub); // critical saves time on resume ExQueueWorkItem(&workItemSetPowerD0->WorkQueueItem, CriticalWorkQueue); // The WorkItem is freed by USBH_SetPowerD0Worker() // Don't try to access the WorkItem after it is queued. ntStatus = STATUS_MORE_PROCESSING_REQUIRED; } else { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } } else { if (!(deviceExtensionHub->HubFlags & HUBFLAG_HUB_STOPPED)) { USBH_SubmitInterruptTransfer(deviceExtensionHub); } } // If we're not going to complete the PowerDeviceD0 request later // in USBH_SetPowerD0Worker(), start the next power IRP here now. // if (ntStatus != STATUS_MORE_PROCESSING_REQUIRED) { PoStartNextPowerIrp(Irp); } break; case PowerDeviceD1: case PowerDeviceD2: case PowerDeviceD3: deviceExtensionHub->CurrentPowerState = irpStack->Parameters.Power.State.DeviceState; break; } USBH_KdPrint((1, "'Setting HU fdo(%x) to D%d, status = %x\n", deviceExtensionHub->FunctionalDeviceObject, irpStack->Parameters.Power.State.DeviceState - 1, ntStatus)); } else { if (irpStack->Parameters.Power.State.DeviceState == PowerDeviceD0) { // Don't forget to start the next power IRP if this is set D0 // and it failed. PoStartNextPowerIrp(Irp); deviceExtensionHub->HubFlags &= ~HUBFLAG_SET_D0_PENDING; } } return ntStatus; } NTSTATUS USBH_FdoDeferPoRequestCompletion( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus ) /*++ Routine Description: This routine is called when the port driver completes an IRP. Arguments: DeviceObject - Pointer to the device object for the class device. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { PIRP irp; PDEVICE_EXTENSION_FDO deviceExtension; PDEVICE_EXTENSION_HUB deviceExtensionHub = NULL; NTSTATUS ntStatus; PIO_STACK_LOCATION irpStack; deviceExtension = Context; irp = deviceExtension->PowerIrp; // return the status of this operation ntStatus = IoStatus->Status; USBH_KdPrint((2,"'USBH_FdoDeferPoRequestCompletion, ntStatus = %x\n", ntStatus)); // It is normal for the power IRP to fail if a hub was removed during // hibernate. // //#if DBG // if (NT_ERROR(ntStatus)) { // USBH_KdTrap(("Device Power Irp Failed (%x)\n", ntStatus)); // } //#endif if (deviceExtension->ExtensionType == EXTENSION_TYPE_HUB) { deviceExtensionHub = Context; } irpStack = IoGetCurrentIrpStackLocation(irp); if (irpStack->Parameters.Power.State.SystemState == PowerSystemWorking && deviceExtensionHub != NULL && IS_ROOT_HUB(deviceExtensionHub)) { // Allow selective suspend once again now that the root hub has // been powered up. LOGENTRY(LOG_PNP, "ESus", deviceExtensionHub, 0, 0); USBH_KdPrint((1,"'Selective Suspend possible again because Root Hub is now at D0\n")); // We know this is the root hub so we don't need to call // USBH_GetRootHubDevExt to get it. deviceExtensionHub->CurrentSystemPowerState = irpStack->Parameters.Power.State.SystemState; } USBH_KdPrint((2,"'irp = %x devobj = %x\n", irp, deviceExtension->TopOfStackDeviceObject)); IoCopyCurrentIrpStackLocationToNext(irp); PoStartNextPowerIrp(irp); PoCallDriver(deviceExtension->TopOfStackDeviceObject, irp); return ntStatus; } VOID USBH_HubQueuePortWakeIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PLIST_ENTRY IrpsToComplete ) /*++ Routine Description: Called to queue all the pending child port WW IRPs of a given hub into a private queue. Arguments: Return Value: The function value is the final status from the operation. --*/ { PDEVICE_EXTENSION_PORT deviceExtensionPort; PUSB_HUB_DESCRIPTOR hubDescriptor; PPORT_DATA portData; PIRP irp; KIRQL irql; ULONG numberOfPorts, i; LONG pendingPortWWs; hubDescriptor = DeviceExtensionHub->HubDescriptor; USBH_ASSERT(NULL != hubDescriptor); numberOfPorts = hubDescriptor->bNumberOfPorts; InitializeListHead(IrpsToComplete); // First, queue all the port wake IRPs into a local list while // the cancel spinlock is held. This will prevent new WW IRPs for // these ports from being submitted while we are traversing the list. // Once we have queued them all we will release the spinlock (because // the list no longer needs protection), then complete the IRPs. IoAcquireCancelSpinLock(&irql); for (i=0; iPortData[i]; if (portData->DeviceObject) { deviceExtensionPort = portData->DeviceObject->DeviceExtension; irp = deviceExtensionPort->WaitWakeIrp; deviceExtensionPort->WaitWakeIrp = NULL; // signal the waitwake irp if we have one if (irp) { IoSetCancelRoutine(irp, NULL); deviceExtensionPort->PortPdoFlags &= ~PORTPDO_REMOTE_WAKEUP_ENABLED; pendingPortWWs = InterlockedDecrement(&DeviceExtensionHub->NumberPortWakeIrps); InsertTailList(IrpsToComplete, &irp->Tail.Overlay.ListEntry); } } } USBH_ASSERT(DeviceExtensionHub->PendingWakeIrp == NULL); IoReleaseCancelSpinLock(irql); } VOID USBH_HubCompleteQueuedPortWakeIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PLIST_ENTRY IrpsToComplete, IN NTSTATUS NtStatus ) /*++ Routine Description: Called to complete all the pending child port WW IRPs in the given private queue. Arguments: Return Value: The function value is the final status from the operation. --*/ { PIRP irp; PLIST_ENTRY listEntry; while (!IsListEmpty(IrpsToComplete)) { listEntry = RemoveHeadList(IrpsToComplete); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); USBH_KdPrint((1,"'Signaling WaitWake IRP (%x)\n", irp)); USBH_CompletePowerIrp(DeviceExtensionHub, irp, NtStatus); } } VOID USBH_HubCompletePortWakeIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN NTSTATUS NtStatus ) /*++ Routine Description: Called when a wake irp completes for a hub Propagates the wake irp completion to all the ports. Arguments: DeviceExtensionHub Return Value: The function value is the final status from the operation. --*/ { LIST_ENTRY irpsToComplete; LOGENTRY(LOG_PNP, "pWWc", DeviceExtensionHub, NtStatus, 0); if (!(DeviceExtensionHub->HubFlags & HUBFLAG_NEED_CLEANUP)) { // Hub has already been removed and child WW IRP's should have already // been completed. LOGENTRY(LOG_PNP, "WWcl", DeviceExtensionHub, 0, 0); return; } USBH_HubQueuePortWakeIrps(DeviceExtensionHub, &irpsToComplete); // Ok, we have queued all the port wake IRPs and have released the // cancel spinlock. Let's complete all the IRPs. USBH_HubCompleteQueuedPortWakeIrps(DeviceExtensionHub, &irpsToComplete, NtStatus); } VOID USBH_HubQueuePortIdleIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PLIST_ENTRY IrpsToComplete ) /*++ Routine Description: Called to queue all the pending child port Idle IRPs of a given hub into a private queue. Arguments: Return Value: The function value is the final status from the operation. --*/ { PDEVICE_EXTENSION_PORT deviceExtensionPort; PUSB_HUB_DESCRIPTOR hubDescriptor; PPORT_DATA portData; PIRP irp; PDRIVER_CANCEL oldCancelRoutine; KIRQL irql; ULONG numberOfPorts, i; hubDescriptor = DeviceExtensionHub->HubDescriptor; USBH_ASSERT(NULL != hubDescriptor); numberOfPorts = hubDescriptor->bNumberOfPorts; InitializeListHead(IrpsToComplete); // First, queue all the port idle IRPs into a local list while // the cancel spinlock is held. This will prevent new WW IRPs for // these ports from being submitted while we are traversing the list. // Once we have queued them all we will release the spinlock (because // the list no longer needs protection), then complete the IRPs. IoAcquireCancelSpinLock(&irql); for (i=0; iPortData[i]; if (portData->DeviceObject) { deviceExtensionPort = portData->DeviceObject->DeviceExtension; irp = deviceExtensionPort->IdleNotificationIrp; deviceExtensionPort->IdleNotificationIrp = NULL; // Complete the Idle IRP if we have one. if (irp) { oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine) { deviceExtensionPort->PortPdoFlags &= ~PORTPDO_IDLE_NOTIFIED; InsertTailList(IrpsToComplete, &irp->Tail.Overlay.ListEntry); } #if DBG else { // // The IRP was cancelled and the cancel routine was called. // The cancel routine will dequeue and complete the IRP, // so don't do it here. USBH_ASSERT(irp->Cancel); } #endif } } } if (DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_IDLE_IRP) { irp = DeviceExtensionHub->PendingIdleIrp; DeviceExtensionHub->PendingIdleIrp = NULL; } else { irp = NULL; ASSERT(!DeviceExtensionHub->PendingIdleIrp); } IoReleaseCancelSpinLock(irql); if (irp) { USBH_HubCancelIdleIrp(DeviceExtensionHub, irp); } } VOID USBH_HubCompleteQueuedPortIdleIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PLIST_ENTRY IrpsToComplete, IN NTSTATUS NtStatus ) /*++ Routine Description: Called to complete all the pending child port Idle IRPs in the given private queue. Arguments: Return Value: The function value is the final status from the operation. --*/ { PIRP irp; PLIST_ENTRY listEntry; while (!IsListEmpty(IrpsToComplete)) { listEntry = RemoveHeadList(IrpsToComplete); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); USBH_KdPrint((1,"'Completing port Idle IRP (%x)\n", irp)); irp->IoStatus.Status = NtStatus; IoCompleteRequest(irp, IO_NO_INCREMENT); } } VOID USBH_HubCompletePortIdleIrps( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN NTSTATUS NtStatus ) /*++ Routine Description: Complete all the Idle IRPs for the given hub. Arguments: DeviceExtensionHub Return Value: The function value is the final status from the operation. --*/ { PDEVICE_EXTENSION_PORT deviceExtensionPort; PUSB_HUB_DESCRIPTOR hubDescriptor; PPORT_DATA portData; PIRP irp; PDRIVER_CANCEL oldCancelRoutine; LIST_ENTRY irpsToComplete; PLIST_ENTRY listEntry; KIRQL irql; ULONG numberOfPorts, i; LOGENTRY(LOG_PNP, "pIIc", DeviceExtensionHub, NtStatus, 0); if (!(DeviceExtensionHub->HubFlags & HUBFLAG_NEED_CLEANUP)) { // Hub has already been removed and child Idle IRP's should have already // been completed. return; } USBH_HubQueuePortIdleIrps(DeviceExtensionHub, &irpsToComplete); // Ok, we have queued all the port idle IRPs and have released the // cancel spinlock. Let's complete all the IRPs. USBH_HubCompleteQueuedPortIdleIrps(DeviceExtensionHub, &irpsToComplete, NtStatus); } VOID USBH_HubCancelWakeIrp( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PIRP Irp ) /*++ Routine Description: Called to cancel the pending WaitWake IRP for a hub. This routine safely cancels the IRP. Note that the pending WaitWake IRP pointer in the hub's device extension should have been already cleared before calling this function. Arguments: Irp - Irp to cancel. Return Value: --*/ { IoCancelIrp(Irp); if (InterlockedExchange(&DeviceExtensionHub->WaitWakeIrpCancelFlag, 1)) { // This IRP has been completed on another thread and the other thread // did not complete the IRP. So, we must complete it here. // // Note that we do not use USBH_CompletePowerIrp as the hub's pending // I/O counter was already decremented on the other thread in the // completion routine. PoStartNextPowerIrp(Irp); Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); } } VOID USBH_HubCancelIdleIrp( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PIRP Irp ) /*++ Routine Description: Called to cancel the pending Idle IRP for a hub. This routine safely cancels the IRP. Note that the pending Idle IRP pointer in the hub's device extension should have been already cleared before calling this function. Arguments: Irp - Irp to cancel. Return Value: --*/ { IoCancelIrp(Irp); if (InterlockedExchange(&DeviceExtensionHub->IdleIrpCancelFlag, 1)) { // This IRP has been completed on another thread and the other thread // did not free the IRP. So, we must free it here. IoFreeIrp(Irp); } } NTSTATUS USBH_FdoPoRequestD0Completion( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus ) /*++ Routine Description: Called when the hub has entered D0 as a result of a wake irp completeing Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus; PDEVICE_EXTENSION_HUB deviceExtensionHub = Context; ntStatus = IoStatus->Status; USBH_KdPrint((1,"'WaitWake D0 completion(%x) for HUB VID %x, PID %x\n", ntStatus, deviceExtensionHub->DeviceDescriptor.idVendor, \ deviceExtensionHub->DeviceDescriptor.idProduct)); LOGENTRY(LOG_PNP, "hWD0", deviceExtensionHub, deviceExtensionHub->PendingWakeIrp, 0); // Since we can't easily determine which ports are asserting resume // signalling we complete the WW IRPs for all of them. // // Ken says that we will need to determine what caused the hub WW // to complete and then only complete the WW Irp for that port, if any. // It is possible for more than one port to assert WW (e.g. user bumped // the mouse at the same time a pressing a key), and it is also possible // for a port with no device to have caused the hub WW to complete (e.g. // device insertion or removal). USBH_HubCompletePortWakeIrps(deviceExtensionHub, STATUS_SUCCESS); // Ok to idle hub again. deviceExtensionHub->HubFlags &= ~HUBFLAG_WW_SET_D0_PENDING; // Also ok to remove hub. USBH_DEC_PENDING_IO_COUNT(deviceExtensionHub); return ntStatus; } NTSTATUS USBH_FdoWaitWakeIrpCompletion( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus ) /*++ Routine Description: Called when a wake irp completes for a hub Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { NTSTATUS ntStatus; ntStatus = IoStatus->Status; return ntStatus; } NTSTATUS USBH_FdoWWIrpIoCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This is the IoCompletionRoutine for the WW IRP for the hub, not to be confused with the PoRequestCompletionRoutine. Arguments: DeviceObject - Pointer to the device object for the class device. Irp - Irp completed. Context - Driver defined context. Return Value: The function value is the final status from the operation. --*/ { PUSBH_COMPLETE_PORT_IRPS_WORK_ITEM workItemCompletePortIrps; NTSTATUS ntStatus; PDEVICE_EXTENSION_HUB deviceExtensionHub = Context; POWER_STATE powerState; KIRQL irql; PIRP irp; ntStatus = Irp->IoStatus.Status; USBH_KdPrint((1,"'WaitWake completion(%x) for HUB VID %x, PID %x\n", ntStatus, deviceExtensionHub->DeviceDescriptor.idVendor, \ deviceExtensionHub->DeviceDescriptor.idProduct)); LOGENTRY(LOG_PNP, "hWWc", deviceExtensionHub, ntStatus, deviceExtensionHub->PendingWakeIrp); // We have to clear the PendingWakeIrp pointer here because in the case // where a device is unplugged between here and when the port loop is // processed in HubCompletePortWakeIrps, we will miss one of the port // WW IRP's, NumberPortWakeIrps will not decrement to zero, and we will // not clear the PendingWakeIrp pointer. This is bad because the IRP // has been completed and the pointer is no longer valid. // // Hopefully the WW IRP for the port will be completed and // NumberPortWakeIrps adjusted properly when the device processes MN_REMOVE. // // BUT: Make sure that we have a PendingWakeIrp first before clearing // because it may have already been cleared when the last port WW was // canceled in USBH_WaitWakeCancel. IoAcquireCancelSpinLock(&irql); // We clear the flag regardless of whether PendingWakeIrp is present or // not because if the WW IRP request in FdoSubmitWaitWakeIrp fails // immediately, PendingWakeIrp will be NULL. deviceExtensionHub->HubFlags &= ~HUBFLAG_PENDING_WAKE_IRP; irp = InterlockedExchangePointer(&deviceExtensionHub->PendingWakeIrp, NULL); // deref the hub, no wake irp is pending USBH_DEC_PENDING_IO_COUNT(deviceExtensionHub); IoReleaseCancelSpinLock(irql); if (NT_SUCCESS(ntStatus)) { // // this means that either we were the source for // the wakeup or a device attached to one of our // ports is. // // our mission now is to discover what caused the // wakeup // USBH_KdPrint((1,"'Remote Wakeup Detected for HUB VID %x, PID %x\n", deviceExtensionHub->DeviceDescriptor.idVendor, \ deviceExtensionHub->DeviceDescriptor.idProduct)); // Prevent idling hub until this Set D0 request completes. deviceExtensionHub->HubFlags |= HUBFLAG_WW_SET_D0_PENDING; // Also prevent hub from being removed before Set D0 is complete. USBH_INC_PENDING_IO_COUNT(deviceExtensionHub); powerState.DeviceState = PowerDeviceD0; // first we need to power up the hub PoRequestPowerIrp(deviceExtensionHub->PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, USBH_FdoPoRequestD0Completion, deviceExtensionHub, NULL); ntStatus = STATUS_SUCCESS; } else { // We complete the port Wake IRPs in a workitem on another // thread so that we don't fail a new Wake IRP for the hub // which might arrive in the same context, before we've // finished completing the old one. workItemCompletePortIrps = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_COMPLETE_PORT_IRPS_WORK_ITEM)); if (workItemCompletePortIrps) { workItemCompletePortIrps->DeviceExtensionHub = deviceExtensionHub; workItemCompletePortIrps->ntStatus = ntStatus; USBH_HubQueuePortWakeIrps(deviceExtensionHub, &workItemCompletePortIrps->IrpsToComplete); ExInitializeWorkItem(&workItemCompletePortIrps->WorkQueueItem, USBH_CompletePortWakeIrpsWorker, workItemCompletePortIrps); LOGENTRY(LOG_PNP, "wITM", deviceExtensionHub, &workItemCompletePortIrps->WorkQueueItem, 0); USBH_INC_PENDING_IO_COUNT(deviceExtensionHub); // critical saves time on resume ExQueueWorkItem(&workItemCompletePortIrps->WorkQueueItem, CriticalWorkQueue); // The WorkItem is freed by USBH_CompletePortWakeIrpsWorker() // Don't try to access the WorkItem after it is queued. } } if (!irp) { // If we have no IRP here this means that another thread wants to // cancel the IRP. Handle accordingly. if (!InterlockedExchange(&deviceExtensionHub->WaitWakeIrpCancelFlag, 1)) { // We got the cancel flag before the other thread did. Hold // on to the IRP here and let the cancel routine complete it. ntStatus = STATUS_MORE_PROCESSING_REQUIRED; } } IoMarkIrpPending(Irp); if (ntStatus != STATUS_MORE_PROCESSING_REQUIRED) { PoStartNextPowerIrp(Irp); } return ntStatus; } NTSTATUS USBH_FdoSubmitWaitWakeIrp( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub ) /*++ Routine Description: called when a child Pdo is enabled for wakeup, this function allocates a wait wake irp and passes it to the parents PDO. Arguments: Return Value: --*/ { PIRP irp; KIRQL irql; NTSTATUS ntStatus; POWER_STATE powerState; USBH_ASSERT(DeviceExtensionHub->PendingWakeIrp == NULL); USBH_KdPrint((1,"'USBH_FdoSubmitWaitWakeIrp (%x)\n", DeviceExtensionHub)); LOGENTRY(LOG_PNP, "hWW_", DeviceExtensionHub, 0, 0); powerState.DeviceState = DeviceExtensionHub->SystemWake; DeviceExtensionHub->HubFlags |= HUBFLAG_PENDING_WAKE_IRP; USBH_INC_PENDING_IO_COUNT(DeviceExtensionHub); InterlockedExchange(&DeviceExtensionHub->WaitWakeIrpCancelFlag, 0); ntStatus = PoRequestPowerIrp(DeviceExtensionHub->PhysicalDeviceObject, IRP_MN_WAIT_WAKE, powerState, USBH_FdoWaitWakeIrpCompletion, DeviceExtensionHub, &irp); USBH_ASSERT(ntStatus == STATUS_PENDING); IoAcquireCancelSpinLock(&irql); if (ntStatus == STATUS_PENDING) { // Must check flag here because in the case where the WW IRP failed // immediately, this flag will be cleared in the completion routine // and if that happens we don't want to save this IRP because it // will soon be invalid if it isn't already. if (DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_WAKE_IRP) { // Successfully posted a Wake IRP. // This hub is now enabled for wakeup. LOGENTRY(LOG_PNP, "hWW+", DeviceExtensionHub, irp, 0); DeviceExtensionHub->PendingWakeIrp = irp; } } else { USBH_ASSERT(FALSE); // Want to know if we ever hit this. DeviceExtensionHub->HubFlags &= ~HUBFLAG_PENDING_WAKE_IRP; USBH_DEC_PENDING_IO_COUNT(DeviceExtensionHub); } IoReleaseCancelSpinLock(irql); return ntStatus; } VOID USBH_FdoIdleNotificationCallback( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub ) /*++ Routine Description: Called when it is time to idle out the hub device itself. Arguments: Return Value: --*/ { PUSB_IDLE_CALLBACK_INFO idleCallbackInfo; PDEVICE_EXTENSION_PORT childDeviceExtensionPort; KIRQL irql; PIRP idleIrp; PIRP irpToCancel = NULL; POWER_STATE powerState; NTSTATUS ntStatus; ULONG i; BOOLEAN bIdleOk = TRUE; LOGENTRY(LOG_PNP, "hId!", DeviceExtensionHub, DeviceExtensionHub->HubFlags, 0); USBH_KdPrint((1,"'Hub %x going idle!\n", DeviceExtensionHub)); if (DeviceExtensionHub->HubFlags & (HUBFLAG_DEVICE_STOPPING | HUBFLAG_HUB_GONE | HUBFLAG_HUB_FAILURE | HUBFLAG_CHILD_DELETES_PENDING | HUBFLAG_WW_SET_D0_PENDING | HUBFLAG_POST_ESD_ENUM_PENDING | HUBFLAG_HUB_HAS_LOST_BRAINS)) { // Don't idle this hub if it was just disconnected or otherwise // being stopped. LOGENTRY(LOG_PNP, "hId.", DeviceExtensionHub, DeviceExtensionHub->HubFlags, 0); USBH_KdPrint((1,"'Hub %x being stopped, in low power, etc., abort idle\n", DeviceExtensionHub)); return; } if (!(DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_WAKE_IRP)) { // If there is not already a WW IRP pending for the hub, submit // one now. This will ensure that the hub will wakeup on connect // change events while it is suspended. ntStatus = USBH_FdoSubmitWaitWakeIrp(DeviceExtensionHub); if (ntStatus != STATUS_PENDING) { LOGENTRY(LOG_PNP, "hIdX", DeviceExtensionHub, ntStatus, 0); UsbhWarning(NULL, "Could not post WW IRP for hub, aborting IDLE.\n", FALSE); return; } } // Ensure that child port configuration does not change while in this // function, i.e. don't allow QBR. USBH_KdPrint((2,"'***WAIT reset device mutex %x\n", DeviceExtensionHub)); USBH_INC_PENDING_IO_COUNT(DeviceExtensionHub); KeWaitForSingleObject(&DeviceExtensionHub->ResetDeviceMutex, Executive, KernelMode, FALSE, NULL); USBH_KdPrint((2,"'***WAIT reset device mutex done %x\n", DeviceExtensionHub)); for (i = 0; i < DeviceExtensionHub->HubDescriptor->bNumberOfPorts; i++) { if (DeviceExtensionHub->PortData[i].DeviceObject) { USBH_KdPrint((1,"'idleCB child PDO %x\n", DeviceExtensionHub->PortData[i].DeviceObject)); childDeviceExtensionPort = DeviceExtensionHub->PortData[i].DeviceObject->DeviceExtension; idleIrp = childDeviceExtensionPort->IdleNotificationIrp; if (idleIrp) { idleCallbackInfo = (PUSB_IDLE_CALLBACK_INFO) IoGetCurrentIrpStackLocation(idleIrp)->\ Parameters.DeviceIoControl.Type3InputBuffer; USBH_ASSERT(idleCallbackInfo && idleCallbackInfo->IdleCallback); if (idleCallbackInfo && idleCallbackInfo->IdleCallback) { // Here we actually call the driver's callback routine, // telling the driver that it is OK to suspend their // device now. LOGENTRY(LOG_PNP, "IdCB", childDeviceExtensionPort, idleCallbackInfo, idleCallbackInfo->IdleCallback); USBH_KdPrint((1,"'FdoIdleNotificationCallback: Calling driver's idle callback routine! %x %x\n", idleCallbackInfo, idleCallbackInfo->IdleCallback)); idleCallbackInfo->IdleCallback(idleCallbackInfo->IdleContext); // Be sure that the child actually powered down. // This is important in the case where the child is also // a hub. Abort if the child aborted. if (childDeviceExtensionPort->DeviceState == PowerDeviceD0) { LOGENTRY(LOG_PNP, "IdAb", childDeviceExtensionPort, idleCallbackInfo, idleCallbackInfo->IdleCallback); USBH_KdPrint((1,"'FdoIdleNotificationCallback: Driver's idle callback routine did not power down! %x %x\n", idleCallbackInfo, idleCallbackInfo->IdleCallback)); bIdleOk = FALSE; break; } } else { // No callback bIdleOk = FALSE; break; } } else { // No Idle IRP bIdleOk = FALSE; break; } } } USBH_KdPrint((2,"'***RELEASE reset device mutex %x\n", DeviceExtensionHub)); KeReleaseSemaphore(&DeviceExtensionHub->ResetDeviceMutex, LOW_REALTIME_PRIORITY, 1, FALSE); USBH_DEC_PENDING_IO_COUNT(DeviceExtensionHub); if (bIdleOk) { // If all the port PDOs have been powered down, // it is time to power down the hub. powerState.DeviceState = DeviceExtensionHub->DeviceWake; PoRequestPowerIrp(DeviceExtensionHub->PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, NULL, NULL, NULL); } else { // One or more of the port PDOs did not have an Idle IRP // (i.e. it was just cancelled), or the Idle IRP did not have a // callback function pointer. Abort this Idle procedure and cancel // the Idle IRP to the hub. LOGENTRY(LOG_PNP, "hIdA", DeviceExtensionHub, DeviceExtensionHub->HubFlags, 0); USBH_KdPrint((1,"'Aborting Idle for Hub %x\n", DeviceExtensionHub)); IoAcquireCancelSpinLock(&irql); if (DeviceExtensionHub && DeviceExtensionHub->PendingIdleIrp) { irpToCancel = DeviceExtensionHub->PendingIdleIrp; DeviceExtensionHub->PendingIdleIrp = NULL; } IoReleaseCancelSpinLock(irql); // Cancel the Idle request to the hub if there is one. if (irpToCancel) { USBH_HubCancelIdleIrp(DeviceExtensionHub, irpToCancel); } USBH_HubCompletePortIdleIrps(DeviceExtensionHub, STATUS_CANCELLED); } } VOID USBH_IdleCompletePowerHubWorker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to power up a hub on completion of an Idle request * for the hub. * * * Arguments: * * Return: * * -- */ { PUSBH_HUB_IDLE_POWER_WORK_ITEM workItemIdlePower; PAGED_CODE(); workItemIdlePower = Context; USBH_HubSetD0(workItemIdlePower->DeviceExtensionHub); USBH_HubCompletePortIdleIrps(workItemIdlePower->DeviceExtensionHub, workItemIdlePower->ntStatus); USBH_DEC_PENDING_IO_COUNT(workItemIdlePower->DeviceExtensionHub); UsbhExFreePool(workItemIdlePower); } VOID USBH_CompletePortIdleIrpsWorker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to complete the child port Idle IRPs * for the hub. * * * Arguments: * * Return: * * -- */ { PUSBH_COMPLETE_PORT_IRPS_WORK_ITEM workItemCompletePortIrps; PAGED_CODE(); workItemCompletePortIrps = Context; USBH_HubCompleteQueuedPortIdleIrps( workItemCompletePortIrps->DeviceExtensionHub, &workItemCompletePortIrps->IrpsToComplete, workItemCompletePortIrps->ntStatus); USBH_DEC_PENDING_IO_COUNT(workItemCompletePortIrps->DeviceExtensionHub); UsbhExFreePool(workItemCompletePortIrps); } VOID USBH_CompletePortWakeIrpsWorker( IN PVOID Context) /* ++ * * Description: * * Work item scheduled to complete the child port Idle IRPs * for the hub. * * * Arguments: * * Return: * * -- */ { PUSBH_COMPLETE_PORT_IRPS_WORK_ITEM workItemCompletePortIrps; PAGED_CODE(); workItemCompletePortIrps = Context; USBH_HubCompleteQueuedPortWakeIrps( workItemCompletePortIrps->DeviceExtensionHub, &workItemCompletePortIrps->IrpsToComplete, workItemCompletePortIrps->ntStatus); USBH_DEC_PENDING_IO_COUNT(workItemCompletePortIrps->DeviceExtensionHub); UsbhExFreePool(workItemCompletePortIrps); } NTSTATUS USBH_FdoIdleNotificationRequestComplete( PDEVICE_OBJECT DeviceObject, PIRP Irp, PDEVICE_EXTENSION_HUB DeviceExtensionHub ) /*++ Routine Description: Completion routine for the Idle request IRP for the hub device. Arguments: Return Value: --*/ { PUSBH_HUB_IDLE_POWER_WORK_ITEM workItemIdlePower; PUSBH_COMPLETE_PORT_IRPS_WORK_ITEM workItemCompletePortIrps; NTSTATUS ntStatus; KIRQL irql; PIRP irp; BOOLEAN bHoldIrp = FALSE; // // DeviceObject is NULL because we sent the irp // UNREFERENCED_PARAMETER(DeviceObject); LOGENTRY(LOG_PNP, "hIdC", DeviceExtensionHub, Irp, Irp->IoStatus.Status); USBH_KdPrint((1,"'Idle notification IRP for hub %x completed %x\n", DeviceExtensionHub, Irp->IoStatus.Status)); USBH_ASSERT(Irp->IoStatus.Status != STATUS_DEVICE_BUSY); IoAcquireCancelSpinLock(&irql); irp = InterlockedExchangePointer(&DeviceExtensionHub->PendingIdleIrp, NULL); DeviceExtensionHub->HubFlags &= ~HUBFLAG_PENDING_IDLE_IRP; USBH_DEC_PENDING_IO_COUNT(DeviceExtensionHub); IoReleaseCancelSpinLock(irql); ntStatus = Irp->IoStatus.Status; // Complete port Idle IRPs w/error if hub Idle IRP failed. // // Skip this if the hub is stopping or has been removed as HubDescriptor // might have already been freed and FdoCleanup will complete these anyway. if (!NT_SUCCESS(ntStatus) && (ntStatus != STATUS_POWER_STATE_INVALID) && !(DeviceExtensionHub->HubFlags & (HUBFLAG_HUB_GONE | HUBFLAG_HUB_STOPPED))) { if (DeviceExtensionHub->CurrentPowerState != PowerDeviceD0) { // Since we are at DPC we must use a work item to power up the // hub synchronously, because that function yields and we can't // yield at DPC level. workItemIdlePower = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_HUB_IDLE_POWER_WORK_ITEM)); if (workItemIdlePower) { workItemIdlePower->DeviceExtensionHub = DeviceExtensionHub; workItemIdlePower->ntStatus = ntStatus; ExInitializeWorkItem(&workItemIdlePower->WorkQueueItem, USBH_IdleCompletePowerHubWorker, workItemIdlePower); LOGENTRY(LOG_PNP, "iITM", DeviceExtensionHub, &workItemIdlePower->WorkQueueItem, 0); USBH_INC_PENDING_IO_COUNT(DeviceExtensionHub); ExQueueWorkItem(&workItemIdlePower->WorkQueueItem, DelayedWorkQueue); // The WorkItem is freed by USBH_IdleCompletePowerHubWorker() // Don't try to access the WorkItem after it is queued. } } else { // We complete the port Idle IRPs in a workitem on another // thread so that we don't fail a new Idle IRP for the hub // which might arrive in the same context, before we've // finished completing the old one. workItemCompletePortIrps = UsbhExAllocatePool(NonPagedPool, sizeof(USBH_COMPLETE_PORT_IRPS_WORK_ITEM)); if (workItemCompletePortIrps) { workItemCompletePortIrps->DeviceExtensionHub = DeviceExtensionHub; workItemCompletePortIrps->ntStatus = ntStatus; USBH_HubQueuePortIdleIrps(DeviceExtensionHub, &workItemCompletePortIrps->IrpsToComplete); ExInitializeWorkItem(&workItemCompletePortIrps->WorkQueueItem, USBH_CompletePortIdleIrpsWorker, workItemCompletePortIrps); LOGENTRY(LOG_PNP, "iIT2", DeviceExtensionHub, &workItemCompletePortIrps->WorkQueueItem, 0); USBH_INC_PENDING_IO_COUNT(DeviceExtensionHub); ExQueueWorkItem(&workItemCompletePortIrps->WorkQueueItem, DelayedWorkQueue); // The WorkItem is freed by USBH_CompletePortIdleIrpsWorker() // Don't try to access the WorkItem after it is queued. } } } if (!irp) { // If we have no IRP here this means that another thread wants to // cancel the IRP. Handle accordingly. if (!InterlockedExchange(&DeviceExtensionHub->IdleIrpCancelFlag, 1)) { // We got the cancel flag before the other thread did. Hold // on to the IRP here and let the cancel routine complete it. bHoldIrp = TRUE; } } // Since we allocated the IRP we must free it, but return // STATUS_MORE_PROCESSING_REQUIRED so the kernel does not try to touch // the IRP after we've freed it. if (!bHoldIrp) { IoFreeIrp(Irp); } return STATUS_MORE_PROCESSING_REQUIRED; } NTSTATUS USBH_FdoSubmitIdleRequestIrp( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub ) /*++ Routine Description: Called when all children PDO's are idled (or there are no children). This function allocates an idle request IOCTL IRP and passes it to the parent's PDO. Arguments: Return Value: --*/ { PIRP irp = NULL; PIO_STACK_LOCATION nextStack; KIRQL irql; NTSTATUS ntStatus; USBH_KdPrint((1,"'USBH_FdoSubmitIdleRequestIrp %x\n", DeviceExtensionHub)); LOGENTRY(LOG_PNP, "hId_", DeviceExtensionHub, 0, 0); USBH_ASSERT(DeviceExtensionHub->PendingIdleIrp == NULL); if (DeviceExtensionHub->PendingIdleIrp) { // Probably don't want to clear the flag here because an Idle IRP // is pending. LOGENTRY(LOG_PNP, "hIb_", DeviceExtensionHub, 0, 0); KeSetEvent(&DeviceExtensionHub->SubmitIdleEvent, 1, FALSE); return STATUS_DEVICE_BUSY; } DeviceExtensionHub->IdleCallbackInfo.IdleCallback = USBH_FdoIdleNotificationCallback; DeviceExtensionHub->IdleCallbackInfo.IdleContext = (PVOID)DeviceExtensionHub; irp = IoAllocateIrp(DeviceExtensionHub->PhysicalDeviceObject->StackSize, FALSE); if (irp == NULL) { // Be sure to set the event and clear the flag on error before exiting. DeviceExtensionHub->HubFlags &= ~HUBFLAG_PENDING_IDLE_IRP; KeSetEvent(&DeviceExtensionHub->SubmitIdleEvent, 1, FALSE); return STATUS_INSUFFICIENT_RESOURCES; } nextStack = IoGetNextIrpStackLocation(irp); nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION; nextStack->Parameters.DeviceIoControl.Type3InputBuffer = &DeviceExtensionHub->IdleCallbackInfo; nextStack->Parameters.DeviceIoControl.InputBufferLength = sizeof(struct _USB_IDLE_CALLBACK_INFO); IoSetCompletionRoutine(irp, USBH_FdoIdleNotificationRequestComplete, DeviceExtensionHub, TRUE, TRUE, TRUE); USBH_INC_PENDING_IO_COUNT(DeviceExtensionHub); InterlockedExchange(&DeviceExtensionHub->IdleIrpCancelFlag, 0); ntStatus = IoCallDriver(DeviceExtensionHub->PhysicalDeviceObject, irp); LOGENTRY(LOG_PNP, "hI>>", DeviceExtensionHub, ntStatus, 0); IoAcquireCancelSpinLock(&irql); if (ntStatus == STATUS_PENDING) { // Must check flag here because in the case where the Idle IRP failed // immediately, this flag will be cleared in the completion routine // and if that happens we don't want to save this IRP because it // will soon be invalid if it isn't already. LOGENTRY(LOG_PNP, "hIpp", DeviceExtensionHub, irp, 0); if (DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_IDLE_IRP) { // Successfully posted an Idle IRP. LOGENTRY(LOG_PNP, "hId+", DeviceExtensionHub, irp, 0); DeviceExtensionHub->PendingIdleIrp = irp; } } IoReleaseCancelSpinLock(irql); KeSetEvent(&DeviceExtensionHub->SubmitIdleEvent, 1, FALSE); return ntStatus; } NTSTATUS USBH_FdoPower( IN PDEVICE_EXTENSION_HUB DeviceExtensionHub, IN PIRP Irp, IN UCHAR MinorFunction ) /* ++ * * Description: * * This function responds to IoControl PnPPower for the FDO. This function is * synchronous. * * Arguments: * * DeviceExtensionHub - the FDO extension pIrp - the request packet * MinorFunction - the minor function of the PnP Power request. * * Return: * * NTSTATUS * * -- */ { PDEVICE_EXTENSION_HUB rootHubDevExt; NTSTATUS ntStatus; PDEVICE_OBJECT deviceObject; PIO_STACK_LOCATION irpStack; BOOLEAN allPDOsAreOff, bHubNeedsWW; PPORT_DATA portData; ULONG i, numberOfPorts; KIRQL irql; PIRP hubWaitWake = NULL; POWER_STATE powerState; irpStack = IoGetCurrentIrpStackLocation(Irp); deviceObject = DeviceExtensionHub->FunctionalDeviceObject; USBH_KdPrint((2,"'Power Request, FDO %x minor %x\n", deviceObject, MinorFunction)); switch (MinorFunction) { // // Pass it down to Pdo to handle these // case IRP_MN_SET_POWER: // // Hub is being asked to change power state // switch (irpStack->Parameters.Power.Type) { case SystemPowerState: { POWER_STATE powerState; LOGENTRY(LOG_PNP, "sysP", DeviceExtensionHub, DeviceExtensionHub->FunctionalDeviceObject, 0); // Track the current system power state in the hub's device ext. // Note that we only set this back to S0 (i.e. allow selective // suspend once again) once the root hub is fully powered up. if (irpStack->Parameters.Power.State.SystemState != PowerSystemWorking) { LOGENTRY(LOG_PNP, "DSus", DeviceExtensionHub, 0, 0); USBH_KdPrint((1,"'Selective Suspend disabled because system is suspending\n")); rootHubDevExt = USBH_GetRootHubDevExt(DeviceExtensionHub); rootHubDevExt->CurrentSystemPowerState = irpStack->Parameters.Power.State.SystemState; } if (irpStack->Parameters.Power.State.SystemState == PowerSystemHibernate) { DeviceExtensionHub->HubFlags |= HUBFLAG_HIBER; USBH_KdPrint((1, "'Hibernate Detected\n")); //TEST_TRAP(); } // map the system state to the appropriate D state. // our policy is: // if we are enabled for wakeup -- go to D2 // else go to D3 USBH_KdPrint( (1, "'IRP_MJ_POWER HU fdo(%x) MN_SET_POWER(SystemPowerState S%x)\n", DeviceExtensionHub->FunctionalDeviceObject, irpStack->Parameters.Power.State.SystemState - 1)); // // walk are list of PDOs, if all are in D3 yje set the // allPDOsAreOff flag allPDOsAreOff = TRUE; portData = DeviceExtensionHub->PortData; // // NOTE: if we are stopped the HubDescriptor will be NULL // if (portData && DeviceExtensionHub->HubDescriptor) { numberOfPorts = DeviceExtensionHub->HubDescriptor->bNumberOfPorts; for (i=0; i < numberOfPorts; i++) { PDEVICE_EXTENSION_PORT deviceExtensionPort; LOGENTRY(LOG_PNP, "cPRT", portData->DeviceObject, 0, 0); if (portData->DeviceObject) { deviceExtensionPort = portData->DeviceObject->DeviceExtension; if (deviceExtensionPort->DeviceState != PowerDeviceD3) { allPDOsAreOff = FALSE; break; } } portData++; } #if DBG // if all PDOs are in D3 then this means the hub itself is a // wakeup source if (DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_WAKE_IRP) { if (allPDOsAreOff) { USBH_KdPrint( (1, "'**Hub enabled for wakeup -- hub is only potential wakeup source\n")); } else { USBH_KdPrint( (1, "'**Hub enabled for wakeup -- device is potential wakeup source\n")); } } #endif } if (irpStack->Parameters.Power.State.SystemState == PowerSystemWorking) { // // go to ON // powerState.DeviceState = PowerDeviceD0; LOGENTRY(LOG_PNP, "syON", 0, 0, 0); } else if ((DeviceExtensionHub->HubFlags & HUBFLAG_PENDING_WAKE_IRP) || !allPDOsAreOff) { // // based on the system power state // request a setting to the appropriate // Dx state. // // all low power states have already been mapped // to suspend powerState.DeviceState = DeviceExtensionHub->DeviceState[irpStack->Parameters.Power.State.SystemState]; // // These tables should have already been fixed up by the root hub // (usbd.sys) to not contain an entry of unspecified. // ASSERT (PowerDeviceUnspecified != powerState.DeviceState); LOGENTRY(LOG_PNP, "syDX", powerState.DeviceState, 0, 0); USBH_KdPrint((1,"'System state maps to device state 0x%x (D%x)\n", powerState.DeviceState, powerState.DeviceState - 1)); } else { powerState.DeviceState = PowerDeviceD3; LOGENTRY(LOG_PNP, "syD3", powerState.DeviceState, 0, 0); } // // only make the request if it is for a different power // state then the one we are in, and it is a valid state for the // request. Also, make sure the hub has been started. // LOGENTRY(LOG_PNP, "H>Sx", DeviceExtensionHub, DeviceExtensionHub->FunctionalDeviceObject, powerState.DeviceState); if (powerState.DeviceState != PowerDeviceUnspecified && powerState.DeviceState != DeviceExtensionHub->CurrentPowerState && (DeviceExtensionHub->HubFlags & HUBFLAG_NEED_CLEANUP)) { DeviceExtensionHub->PowerIrp = Irp; IoMarkIrpPending(Irp); ntStatus = PoRequestPowerIrp(DeviceExtensionHub->PhysicalDeviceObject, IRP_MN_SET_POWER, powerState, USBH_FdoDeferPoRequestCompletion, DeviceExtensionHub, NULL); USBH_KdPrint((2,"'PoRequestPowerIrp returned 0x%x\n", ntStatus)); // We need to return STATUS_PENDING here because we marked the // IRP pending above with IoMarkIrpPending. USBH_ASSERT(ntStatus == STATUS_PENDING); // In the case where an allocation failed, PoRequestPowerIrp // can return a status code other than STATUS_PENDING. In this // case, we still need to pass the IRP down to the lower driver, // but we still need to return STATUS_PENDING from this routine. if (ntStatus != STATUS_PENDING) { IoCopyCurrentIrpStackLocationToNext(Irp); PoStartNextPowerIrp(Irp); ntStatus = PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); } ntStatus = STATUS_PENDING; } else { IoCopyCurrentIrpStackLocationToNext(Irp); PoStartNextPowerIrp(Irp); ntStatus = PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); } } break; //SystemPowerState case DevicePowerState: USBH_KdPrint( (1, "'IRP_MJ_POWER HU fdo(%x) MN_SET_POWER(DevicePowerState D%x)\n", DeviceExtensionHub->FunctionalDeviceObject, irpStack->Parameters.Power.State.DeviceState - 1)); LOGENTRY(LOG_PNP, "H>Dx", DeviceExtensionHub, DeviceExtensionHub->FunctionalDeviceObject, irpStack->Parameters.Power.State.DeviceState); // If we are already in the requested power state, or if this is // a Set D0 request and we already have one pending, // just pass the request on. if ((DeviceExtensionHub->CurrentPowerState == irpStack->Parameters.Power.State.DeviceState) || (irpStack->Parameters.Power.State.DeviceState == PowerDeviceD0 && (DeviceExtensionHub->HubFlags & HUBFLAG_SET_D0_PENDING))) { LOGENTRY(LOG_PNP, "HDxP", DeviceExtensionHub, 0, 0); IoCopyCurrentIrpStackLocationToNext(Irp); PoStartNextPowerIrp(Irp); IoMarkIrpPending(Irp); PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); ntStatus = STATUS_PENDING; break; } switch (irpStack->Parameters.Power.State.DeviceState) { case PowerDeviceD0: USBH_ASSERT(DeviceExtensionHub->CurrentPowerState != PowerDeviceD0); DeviceExtensionHub->HubFlags &= ~(HUBFLAG_DEVICE_STOPPING | HUBFLAG_DEVICE_LOW_POWER); DeviceExtensionHub->HubFlags |= HUBFLAG_SET_D0_PENDING; // // must pass this on to our PDO // IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, USBH_PowerIrpCompletion, DeviceExtensionHub, TRUE, TRUE, TRUE); IoMarkIrpPending(Irp); PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); // For some strange PnP reason, we have to return // STATUS_PENDING here if our completion routine will also // pend (e.g. return STATUS_MORE_PROCESSING_REQUIRED). // (Ignore the PoCallDriver return value.) ntStatus = STATUS_PENDING; break; case PowerDeviceD1: case PowerDeviceD2: case PowerDeviceD3: // If there is a ChangeIndicationWorkitem pending, then we // must wait for that to complete. if (DeviceExtensionHub->ChangeIndicationWorkitemPending) { USBH_KdPrint((2,"'Wait for single object\n")); ntStatus = KeWaitForSingleObject(&DeviceExtensionHub->CWKEvent, Suspended, KernelMode, FALSE, NULL); USBH_KdPrint((2,"'Wait for single object, returned %x\n", ntStatus)); } // // set our stop flag so that ChangeIndication does not submit // any more transfers // // note that we skip this if the hub is 'stopped' if (!(DeviceExtensionHub->HubFlags & HUBFLAG_HUB_STOPPED)) { NTSTATUS status; BOOLEAN bRet; DeviceExtensionHub->HubFlags |= (HUBFLAG_DEVICE_STOPPING | HUBFLAG_DEVICE_LOW_POWER); bRet = IoCancelIrp(DeviceExtensionHub->Irp); // always wait -- this fixes a bosd on an IBM laptop // the if was a hack someone put in but we have no idea why // if (bRet) { LOGENTRY(LOG_PNP, "aWAT", DeviceExtensionHub, &DeviceExtensionHub->AbortEvent, bRet); status = KeWaitForSingleObject( &DeviceExtensionHub->AbortEvent, Suspended, KernelMode, FALSE, NULL); LOGENTRY(LOG_PNP, "awat", DeviceExtensionHub, 0, status); } // // must pass this on to our PDO // IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, USBH_PowerIrpCompletion, DeviceExtensionHub, TRUE, TRUE, TRUE); PoStartNextPowerIrp(Irp); IoMarkIrpPending(Irp); PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); // toss status and return status pending // we do this because our completion routine // stalls completion but we do not block here // in dispatch. // OS code only waits if status_pending is returned ntStatus = STATUS_PENDING; break; } break; //DevicePowerState } break; // MN_SET_POWER case IRP_MN_QUERY_POWER: USBH_KdPrint((1, "'IRP_MJ_POWER HU fdo(%x) MN_QUERY_POWER\n", DeviceExtensionHub->FunctionalDeviceObject)); // Cancel our WW IRP if we are going to D3, the hub is idled // (selectively suspended), and the hub is empty. We don't want // to prevent going to D3 if the hub is selectively suspended and // there are no children that would require the hub be wake-enabled. powerState.DeviceState = DeviceExtensionHub->DeviceState[irpStack->Parameters.Power.State.SystemState]; bHubNeedsWW = USBH_DoesHubNeedWaitWake(DeviceExtensionHub); IoAcquireCancelSpinLock(&irql); if (powerState.DeviceState == PowerDeviceD3 && DeviceExtensionHub->PendingWakeIrp && !bHubNeedsWW) { hubWaitWake = DeviceExtensionHub->PendingWakeIrp; DeviceExtensionHub->PendingWakeIrp = NULL; } IoReleaseCancelSpinLock(irql); if (hubWaitWake) { USBH_KdPrint((1, "'Cancelling hub's WW because we are going to D3 and there are no children\n")); USBH_HubCancelWakeIrp(DeviceExtensionHub, hubWaitWake); } // // Now pass this on to our PDO. // IoCopyCurrentIrpStackLocationToNext(Irp); PoStartNextPowerIrp(Irp); ntStatus = PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); break; case IRP_MN_WAIT_WAKE: USBH_KdPrint((1, "'IRP_MJ_POWER HU fdo(%x) MN_WAIT_WAKE\n", DeviceExtensionHub->FunctionalDeviceObject)); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, USBH_FdoWWIrpIoCompletion, DeviceExtensionHub, TRUE, TRUE, TRUE); PoStartNextPowerIrp(Irp); IoMarkIrpPending(Irp); PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); // For some strange PnP reason, we have to return // STATUS_PENDING here if our completion routine will also // pend (e.g. return STATUS_MORE_PROCESSING_REQUIRED). // (Ignore the PoCallDriver return value.) ntStatus = STATUS_PENDING; break; // // otherwise pass the Irp down // default: USBH_KdPrint((2,"'Unhandled Power request to fdo %x %x, passed to PDO\n", deviceObject, MinorFunction)); IoCopyCurrentIrpStackLocationToNext(Irp); PoStartNextPowerIrp(Irp); ntStatus = PoCallDriver(DeviceExtensionHub->TopOfStackDeviceObject, Irp); break; } USBH_KdPrint((2,"'FdoPower exit %x\n", ntStatus)); return ntStatus; }