windows-nt/Source/XPSP1/NT/drivers/wdm/usb/usbccgp/parent.c
2020-09-26 16:20:57 +08:00

1628 lines
52 KiB
C

/*
*************************************************************************
* File: PARENT.C
*
* Module: USBCCGP.SYS
* USB Common Class Generic Parent driver.
*
* Copyright (c) 1998 Microsoft Corporation
*
*
* Author: ervinp
*
*************************************************************************
*/
#include <wdm.h>
#include <usbdi.h>
#include <usbdlib.h>
#include <usbioctl.h>
#include <ntddstor.h>
#include "usbccgp.h"
#include "security.h"
#include "debug.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, GetInterfaceList)
#pragma alloc_text(PAGE, GetConfigDescriptor)
#pragma alloc_text(PAGE, TryGetConfigDescriptor)
#pragma alloc_text(PAGE, GetDeviceDescriptor)
#pragma alloc_text(PAGE, GetParentFdoCapabilities)
#pragma alloc_text(PAGE, StartParentFdo)
#pragma alloc_text(PAGE, QueryParentDeviceRelations)
#endif
/*
********************************************************************************
* SubmitUrb
********************************************************************************
*
*
* Send the URB to the USB device.
* If synchronous is TRUE,
* ignore the completion info and synchonize the IRP;
* otherwise, don't synchronize and set the provided completion
* routine for the IRP.
*/
NTSTATUS SubmitUrb( PPARENT_FDO_EXT parentFdoExt,
PURB urb,
BOOLEAN synchronous,
PVOID completionRoutine,
PVOID completionContext)
{
PIRP irp;
NTSTATUS status;
/*
* Allocate the IRP to send the buffer down the USB stack.
*
* Don't use IoBuildDeviceIoControlRequest (because it queues
* the IRP on the current thread's irp list and may
* cause the calling process to hang if the IopCompleteRequest APC
* does not fire and dequeue the IRP).
*/
irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);
if (irp){
PIO_STACK_LOCATION nextSp = IoGetNextIrpStackLocation(irp);
nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
/*
* Attach the URB to this IRP.
*/
nextSp->Parameters.Others.Argument1 = urb;
if (synchronous){
status = CallNextDriverSync(parentFdoExt, irp);
IoFreeIrp(irp);
}
else {
/*
* Caller's completion routine will free the irp when it completes.
* It will also decrement the pendingActionCount.
*/
ASSERT(completionRoutine);
ASSERT(completionContext);
IoSetCompletionRoutine( irp,
completionRoutine,
completionContext,
TRUE, TRUE, TRUE);
IncrementPendingActionCount(parentFdoExt);
status = IoCallDriver(parentFdoExt->topDevObj, irp);
}
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
return status;
}
/*
* GetInterfaceList
*
*
*/
PUSBD_INTERFACE_LIST_ENTRY GetInterfaceList(
PPARENT_FDO_EXT parentFdoExt,
PUSB_CONFIGURATION_DESCRIPTOR configDesc)
{
PUSBD_INTERFACE_LIST_ENTRY interfaceList;
PAGED_CODE();
if (configDesc->bNumInterfaces > 0){
interfaceList = ALLOCPOOL( NonPagedPool,
(configDesc->bNumInterfaces+1) * sizeof(USBD_INTERFACE_LIST_ENTRY));
if (interfaceList){
ULONG i;
/*
* Parse out the interface descriptors
*/
for (i = 0; i < configDesc->bNumInterfaces; i++){
PUSB_INTERFACE_DESCRIPTOR interfaceDesc;
interfaceDesc = USBD_ParseConfigurationDescriptorEx(
configDesc,
configDesc,
i,
0,
-1,
-1,
-1);
ASSERT(interfaceDesc);
interfaceList[i].InterfaceDescriptor = interfaceDesc;
/*
* The .Interface field will be filled in when we do the SELECT_CONFIG.
*/
interfaceList[i].Interface = BAD_POINTER;
}
/*
* Terminate the list.
*/
interfaceList[i].InterfaceDescriptor = NULL;
interfaceList[i].Interface = NULL;
}
else {
TRAP("Memory allocation failed");
}
}
else {
ASSERT(configDesc->bNumInterfaces > 0);
interfaceList = NULL;
}
ASSERT(interfaceList);
return interfaceList;
}
VOID FreeInterfaceList(PPARENT_FDO_EXT parentFdoExt, BOOLEAN freeListItself)
{
if (ISPTR(parentFdoExt->interfaceList)){
ULONG i;
for (i = 0; i < parentFdoExt->configDesc->bNumInterfaces; i++){
if (ISPTR(parentFdoExt->interfaceList[i].Interface)){
PUSBD_INTERFACE_LIST_ENTRY iface = &parentFdoExt->interfaceList[i];
ASSERT(iface->Interface->Length >= FIELD_OFFSET(USBD_INTERFACE_INFORMATION, Pipes));
FREEPOOL(iface->Interface);
iface->Interface = BAD_POINTER;
}
}
if (freeListItself){
FREEPOOL(parentFdoExt->interfaceList);
parentFdoExt->interfaceList = BAD_POINTER;
}
}
}
NTSTATUS ParentSelectConfiguration( PPARENT_FDO_EXT parentFdoExt,
PUSB_CONFIGURATION_DESCRIPTOR configDesc,
PUSBD_INTERFACE_LIST_ENTRY interfaceList)
{
NTSTATUS status;
PURB urb;
/*
* Use USBD_CreateConfigurationRequestEx to allocate
* an URB of the right size (including the appended
* interface and pipe info we'll get back from
* the URB_FUNCTION_SELECT_CONFIGURATION urb).
*/
urb = USBD_CreateConfigurationRequestEx(configDesc, interfaceList);
if (urb){
status = SubmitUrb(parentFdoExt, urb, TRUE, NULL, NULL);
if (NT_SUCCESS(status)){
ULONG i;
/*
* This new SELECT_CONFIGURATION URB call caused
* USBD_SelectConfiguration to close the current
* configuration handle. So we need to update
* our handles.
*/
parentFdoExt->selectedConfigHandle = urb->UrbSelectConfiguration.ConfigurationHandle;
/*
* Each interfaceList's Interface pointer points
* to a part of the URB's buffer. So copy these
* out before freeing the urb.
*/
for (i = 0; i < configDesc->bNumInterfaces; i++){
PVOID ifaceInfo = interfaceList[i].Interface;
if (ifaceInfo){
ULONG len = interfaceList[i].Interface->Length;
ASSERT(len >= FIELD_OFFSET(USBD_INTERFACE_INFORMATION, Pipes));
interfaceList[i].Interface = ALLOCPOOL(NonPagedPool, len);
if (interfaceList[i].Interface){
RtlCopyMemory(interfaceList[i].Interface, ifaceInfo, len);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
}
else {
ASSERT(ifaceInfo);
status = STATUS_UNSUCCESSFUL;
break;
}
}
}
else {
DBGWARN(("URB_FUNCTION_SELECT_CONFIGURATION failed with %xh", status));
}
FREEPOOL(urb);
}
else {
DBGERR(("USBD_CreateConfigurationRequest... failed"));
status = STATUS_INSUFFICIENT_RESOURCES;
}
return status;
}
VOID ParentCloseConfiguration(PPARENT_FDO_EXT parentFdoExt)
{
URB urb;
NTSTATUS status;
urb.UrbHeader.Length = sizeof(struct _URB_SELECT_CONFIGURATION);
urb.UrbHeader.Function = URB_FUNCTION_SELECT_CONFIGURATION;
urb.UrbSelectConfiguration.ConfigurationDescriptor = NULL;
status = SubmitUrb(parentFdoExt, &urb, TRUE, NULL, NULL);
ASSERT(NT_SUCCESS(status));
}
/*
* TryGetConfigDescriptor
*
* Try to get configuration descriptor for device .
*
*
*/
NTSTATUS TryGetConfigDescriptor(PPARENT_FDO_EXT parentFdoExt)
{
URB urb;
NTSTATUS status;
USB_CONFIGURATION_DESCRIPTOR configDescBase = {0};
PAGED_CODE();
/*
* Get the first part of the configuration descriptor.
* It will tell us the size of the full configuration descriptor,
* including all the following interface descriptors, etc.
*/
UsbBuildGetDescriptorRequest(&urb,
(USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
USB_CONFIGURATION_DESCRIPTOR_TYPE,
0,
0,
(PVOID)&configDescBase,
NULL,
sizeof(USB_CONFIGURATION_DESCRIPTOR),
NULL);
status = SubmitUrb(parentFdoExt, &urb, TRUE, NULL, NULL);
if (NT_SUCCESS(status)){
ULONG configDescLen = configDescBase.wTotalLength;
/*
* Now allocate the right-sized buffer for the full configuration descriptor.
*/
ASSERT(configDescLen < 0x1000);
parentFdoExt->configDesc = ALLOCPOOL(NonPagedPool, configDescLen);
if (parentFdoExt->configDesc){
RtlZeroMemory(parentFdoExt->configDesc, configDescLen);
UsbBuildGetDescriptorRequest(&urb,
(USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
USB_CONFIGURATION_DESCRIPTOR_TYPE,
0,
0,
parentFdoExt->configDesc,
NULL,
configDescLen,
NULL);
status = SubmitUrb(parentFdoExt, &urb, TRUE, NULL, NULL);
if (NT_SUCCESS(status)){
ASSERT(urb.UrbControlDescriptorRequest.TransferBufferLength == configDescLen);
ASSERT(parentFdoExt->configDesc->wTotalLength == configDescLen);
DBGDUMPBYTES("Got config desc", parentFdoExt->configDesc, parentFdoExt->configDesc->wTotalLength);
parentFdoExt->selectedConfigDesc = parentFdoExt->configDesc;
parentFdoExt->selectedConfigHandle = urb.UrbSelectConfiguration.ConfigurationHandle;
} else {
/*
* Deallocate the configDesc buffer if URB submission failed.
*/
FREEPOOL(parentFdoExt->configDesc);
parentFdoExt->configDesc = NULL;
}
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
return status;
}
/*
* GetConfigDescriptor
*
* Get configuration descriptor for device.
* Some devices (expecially speakers, which can have huge config descriptors)
* are flaky returning their config descriptors. So we try up to 3 times.
*/
NTSTATUS GetConfigDescriptor(PPARENT_FDO_EXT parentFdoExt)
{
const ULONG numAttempts = 3;
NTSTATUS status;
ULONG i;
PAGED_CODE();
for (i = 1; i <= numAttempts; i++){
status = TryGetConfigDescriptor(parentFdoExt);
if (NT_SUCCESS(status)){
if (i != 1) DBGOUT(("GetConfigDescriptor: got config descriptor on retry (@ %ph)", parentFdoExt->configDesc));
break;
}
else {
if (i < numAttempts){
DBGWARN(("GetConfigDescriptor: failed with %xh (attempt #%d).", status, i));
}
else {
DBGWARN(("GetConfigDescriptor: failed %d times (status = %xh).", numAttempts, status));
}
}
}
return status;
}
NTSTATUS GetDeviceDescriptor(PPARENT_FDO_EXT parentFdoExt)
{
URB urb;
NTSTATUS status;
PAGED_CODE();
RtlZeroMemory(&parentFdoExt->deviceDesc, sizeof(parentFdoExt->deviceDesc));
UsbBuildGetDescriptorRequest(&urb,
(USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
USB_DEVICE_DESCRIPTOR_TYPE,
0,
0,
(PVOID)&parentFdoExt->deviceDesc,
NULL,
sizeof(parentFdoExt->deviceDesc),
NULL);
status = SubmitUrb(parentFdoExt, &urb, TRUE, NULL, NULL);
if (NT_SUCCESS(status)){
DBGVERBOSE(("Got device desc @ %ph, len=%xh (should be %xh).", (PVOID)&parentFdoExt->deviceDesc, urb.UrbControlDescriptorRequest.TransferBufferLength, sizeof(parentFdoExt->deviceDesc)));
}
return status;
}
VOID PrepareParentFDOForRemove(PPARENT_FDO_EXT parentFdoExt)
{
enum deviceState oldState;
KIRQL oldIrql;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
oldState = parentFdoExt->state;
parentFdoExt->state = STATE_REMOVING;
/*
* Careful, we may not have allocated the deviceRelations if the previous start failed.
*/
if (ISPTR(parentFdoExt->deviceRelations)){
DBGVERBOSE(("PrepareParentFDOForRemove: removing %d child PDOs.", parentFdoExt->deviceRelations->Count));
while (parentFdoExt->deviceRelations->Count > 0){
PDEVICE_OBJECT devObj;
PDEVEXT devExt;
PFUNCTION_PDO_EXT functionPdoExt;
/*
* Remove the last child pdo from the parent's deviceRelations.
*/
parentFdoExt->deviceRelations->Count--;
devObj = parentFdoExt->deviceRelations->Objects[parentFdoExt->deviceRelations->Count];
parentFdoExt->deviceRelations->Objects[parentFdoExt->deviceRelations->Count] = BAD_POINTER;
ASSERT(devObj->Type == IO_TYPE_DEVICE);
devExt = devObj->DeviceExtension;
ASSERT(!devExt->isParentFdo);
functionPdoExt = &devExt->functionPdoExt;
/*
* Free this child pdo. Must drop spinlock around call outside driver.
*/
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
FreeFunctionPDOResources(functionPdoExt);
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if ((oldState != STATE_REMOVING) && (oldState != STATE_REMOVED)){
/*
* Do an extra decrement on the pendingActionCount.
* This will cause the count to eventually go to -1
* (once all IO completes),
* at which time we'll continue.
*/
DecrementPendingActionCount(parentFdoExt);
KeWaitForSingleObject( &parentFdoExt->removeEvent,
Executive,
KernelMode,
FALSE,
NULL );
}
}
VOID FreeParentFDOResources(PPARENT_FDO_EXT parentFdoExt)
{
parentFdoExt->state = STATE_REMOVED;
FreeInterfaceList(parentFdoExt, TRUE);
// It is possible that after a failed start, the deviceRelations
// and configDesc buffers will not have been allocated.
if (ISPTR(parentFdoExt->deviceRelations)){
FREEPOOL(parentFdoExt->deviceRelations);
}
parentFdoExt->deviceRelations = BAD_POINTER;
if (ISPTR(parentFdoExt->configDesc)){
FREEPOOL(parentFdoExt->configDesc);
}
parentFdoExt->configDesc = BAD_POINTER;
parentFdoExt->selectedConfigDesc = BAD_POINTER;
if (ISPTR(parentFdoExt->msExtConfigDesc)){
FREEPOOL(parentFdoExt->msExtConfigDesc);
}
parentFdoExt->msExtConfigDesc = BAD_POINTER;
/*
* Delete the device object. This will also delete the device extension.
*/
IoDeleteDevice(parentFdoExt->fdo);
}
/*
* GetParentFdoCapabilities
*
*/
NTSTATUS GetParentFdoCapabilities(PPARENT_FDO_EXT parentFdoExt)
{
NTSTATUS status;
PIRP irp;
PAGED_CODE();
irp = IoAllocateIrp(parentFdoExt->pdo->StackSize, FALSE);
if (irp){
PIO_STACK_LOCATION nextSp = IoGetNextIrpStackLocation(irp);
nextSp->MajorFunction= IRP_MJ_PNP;
nextSp->MinorFunction= IRP_MN_QUERY_CAPABILITIES;
RtlZeroMemory(&parentFdoExt->deviceCapabilities, sizeof(DEVICE_CAPABILITIES));
parentFdoExt->deviceCapabilities.Size = sizeof(DEVICE_CAPABILITIES);
parentFdoExt->deviceCapabilities.Version = 1;
parentFdoExt->deviceCapabilities.Address = -1;
parentFdoExt->deviceCapabilities.UINumber = -1;
nextSp->Parameters.DeviceCapabilities.Capabilities = &parentFdoExt->deviceCapabilities;
irp->IoStatus.Status = STATUS_NOT_SUPPORTED; // default status for PNP irps is STATUS_NOT_SUPPORTED
status = CallDriverSync(parentFdoExt->topDevObj, irp);
IoFreeIrp(irp);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(NT_SUCCESS(status));
return status;
}
/*
************************************************************
* StartParentFdo
************************************************************
*
*/
NTSTATUS StartParentFdo(PPARENT_FDO_EXT parentFdoExt, PIRP irp)
{
NTSTATUS status;
BOOLEAN resumingFromStop;
PAGED_CODE();
resumingFromStop = ((parentFdoExt->state == STATE_STOPPING) || (parentFdoExt->state == STATE_STOPPED));
parentFdoExt->state = STATE_STARTING;
/*
* Chain the call down the stack synchronously first
* (have to start the lower stack before sending other calls to it).
*/
IoCopyCurrentIrpStackLocationToNext(irp);
status = CallDriverSync(parentFdoExt->topDevObj, irp);
if (NT_SUCCESS(status)){
if (resumingFromStop){
/*
* If we're resuming from a STOP, we don't need to get descriptors, etc., again.
* All we have to do is a SELECT_CONFIGURATION.
* The function PDOs are presumably already created and are already
* pointing into the parent's interfaceList.
*
* ** This call will change the .Interface fields inside each element
* of the parent's interfaceList; when we're done, the interfaceList
* AND the function PDOs' pointers into that list will be valid.
*/
status = ParentSelectConfiguration( parentFdoExt,
parentFdoExt->selectedConfigDesc,
parentFdoExt->interfaceList);
}
else {
status = GetDeviceDescriptor(parentFdoExt);
if (NT_SUCCESS(status)){
status = GetConfigDescriptor(parentFdoExt);
if (NT_SUCCESS(status)){
status = GetParentFdoCapabilities(parentFdoExt);
if (NT_SUCCESS(status)){
if (NT_SUCCESS(status)){
parentFdoExt->interfaceList = GetInterfaceList(parentFdoExt, parentFdoExt->selectedConfigDesc);
if (parentFdoExt->interfaceList){
status = ParentSelectConfiguration( parentFdoExt,
parentFdoExt->selectedConfigDesc,
parentFdoExt->interfaceList);
GetMsExtendedConfigDescriptor(parentFdoExt);
if (NT_SUCCESS(status)){
status = CreateStaticFunctionPDOs(parentFdoExt);
if (NT_SUCCESS(status)){
/*
* Alert the system that we are creating
* new PDOs. The kernel should respond by
* sending us the
* IRP_MN_QUERY_DEVICE_RELATIONS PnP IRP.
*/
IoInvalidateDeviceRelations(parentFdoExt->pdo, BusRelations);
}
else {
if (parentFdoExt->deviceRelations) {
FREEPOOL(parentFdoExt->deviceRelations);
}
parentFdoExt->deviceRelations = BAD_POINTER;
}
}
if (!NT_SUCCESS(status)){
FREEPOOL(parentFdoExt->interfaceList);
parentFdoExt->interfaceList = BAD_POINTER;
}
}
else {
status = STATUS_DEVICE_DATA_ERROR;
}
}
}
}
}
}
}
else {
DBGWARN(("Chained start irp failed with %xh.", status));
}
if (NT_SUCCESS(status)){
parentFdoExt->state = STATE_STARTED;
}
else {
DBGWARN(("StartParentFdo failed with %xh.", status));
parentFdoExt->state = STATE_START_FAILED;
}
return status;
}
/*
********************************************************************************
* QueryParentDeviceRelations
********************************************************************************
*
*
*/
NTSTATUS QueryParentDeviceRelations(PPARENT_FDO_EXT parentFdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp;
NTSTATUS status;
PAGED_CODE();
irpSp = IoGetCurrentIrpStackLocation(irp);
if (irpSp->Parameters.QueryDeviceRelations.Type == BusRelations){
if (parentFdoExt->deviceRelations){
/*
* NTKERN expects a new pointer each time it calls QUERY_DEVICE_RELATIONS;
* it then FREES THE POINTER.
* So we have to return a new pointer each time, whether or not we actually
* created our copy of the device relations for this call.
*/
irp->IoStatus.Information = (ULONG_PTR)CopyDeviceRelations(parentFdoExt->deviceRelations);
if (irp->IoStatus.Information){
ULONG i;
/*
* The kernel dereferences each device object
* in the device relations list after each call.
* So for each call, add an extra reference.
*/
for (i = 0; i < parentFdoExt->deviceRelations->Count; i++){
ObReferenceObject(parentFdoExt->deviceRelations->Objects[i]);
parentFdoExt->deviceRelations->Objects[i]->Flags &= ~DO_DEVICE_INITIALIZING;
}
DBGVERBOSE(("Parent returned %d child PDOs", parentFdoExt->deviceRelations->Count));
/*
* If we are succeeding this PnP IRP, then we pass it down
* the stack but change its default status to success.
*/
irp->IoStatus.Status = STATUS_SUCCESS;
status = NO_STATUS;
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
else {
ASSERT(parentFdoExt->deviceRelations);
status = STATUS_DEVICE_DATA_ERROR;
}
}
else {
/*
* Pass this IRP down to the next driver.
*/
status = NO_STATUS;
}
if (!NT_SUCCESS(status) && (status != NO_STATUS)) {
DBGWARN(("QueryParentDeviceRelations: failed with %xh.", status));
}
return status;
}
/*
********************************************************************************
* ParentPowerRequestCompletion
********************************************************************************
*
*
*/
VOID ParentPowerRequestCompletion(
IN PDEVICE_OBJECT devObj,
IN UCHAR minorFunction,
IN POWER_STATE powerState,
IN PVOID context,
IN PIO_STATUS_BLOCK ioStatus)
{
PPARENT_FDO_EXT parentFdoExt = (PPARENT_FDO_EXT)context;
PIRP parentSetPowerIrp;
ASSERT(parentFdoExt->currentSetPowerIrp->Type == IO_TYPE_IRP);
parentSetPowerIrp = parentFdoExt->currentSetPowerIrp;
parentFdoExt->currentSetPowerIrp = NULL;
/*
* This is the completion routine for the device-state power
* Irp which we've requested. Complete the original system-state
* power Irp with the result of the device-state power Irp.
*/
ASSERT(devObj->Type == IO_TYPE_DEVICE);
ASSERT(NT_SUCCESS(ioStatus->Status));
parentSetPowerIrp->IoStatus.Status = ioStatus->Status;
PoStartNextPowerIrp(parentSetPowerIrp);
if (NT_SUCCESS(ioStatus->Status)){
IoCopyCurrentIrpStackLocationToNext(parentSetPowerIrp);
IoSetCompletionRoutine(parentSetPowerIrp, ParentPdoPowerCompletion, (PVOID)parentFdoExt, TRUE, TRUE, TRUE);
PoCallDriver(parentFdoExt->topDevObj, parentSetPowerIrp);
}
else {
IoCompleteRequest(parentSetPowerIrp, IO_NO_INCREMENT);
}
}
/*
********************************************************************************
* ParentPdoPowerCompletion
********************************************************************************
*
*
*/
NTSTATUS ParentPdoPowerCompletion(IN PDEVICE_OBJECT devObj, IN PIRP irp, IN PVOID context)
{
PIO_STACK_LOCATION irpSp;
PPARENT_FDO_EXT parentFdoExt = (PPARENT_FDO_EXT)context;
ASSERT(parentFdoExt);
irpSp = IoGetCurrentIrpStackLocation(irp);
ASSERT(irpSp->MajorFunction == IRP_MJ_POWER);
if (NT_SUCCESS(irp->IoStatus.Status)){
switch (irpSp->MinorFunction){
case IRP_MN_SET_POWER:
switch (irpSp->Parameters.Power.Type){
case DevicePowerState:
switch (irpSp->Parameters.Power.State.DeviceState){
case PowerDeviceD0:
if (parentFdoExt->state == STATE_SUSPENDED){
parentFdoExt->state = STATE_STARTED;
CompleteAllFunctionWaitWakeIrps(parentFdoExt, STATUS_SUCCESS);
CompleteAllFunctionIdleIrps(parentFdoExt, STATUS_SUCCESS);
}
break;
}
break;
}
break;
}
}
/*
* Must propagate the pending bit if a lower driver returned pending.
*/
if (irp->PendingReturned){
IoMarkIrpPending(irp);
}
return STATUS_SUCCESS;
}
/*
* HandleParentFdoPower
*
*
*/
NTSTATUS HandleParentFdoPower(PPARENT_FDO_EXT parentFdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp;
BOOLEAN completeIrpHere = FALSE;
BOOLEAN justReturnPending = FALSE;
NTSTATUS status = NO_STATUS;
KIRQL oldIrql;
irpSp = IoGetCurrentIrpStackLocation(irp);
if ((parentFdoExt->state == STATE_REMOVING) ||
(parentFdoExt->state == STATE_REMOVED)){
status = STATUS_DEVICE_NOT_CONNECTED;
completeIrpHere = TRUE;
}
else {
switch (irpSp->MinorFunction){
case IRP_MN_SET_POWER:
switch (irpSp->Parameters.Power.Type){
case SystemPowerState:
{
SYSTEM_POWER_STATE systemState = irpSp->Parameters.Power.State.SystemState;
ASSERT((ULONG)systemState < PowerSystemMaximum);
if (systemState <= PowerSystemHibernate){
/*
* For the 'regular' system power states,
* we convert to a device power state
* and request a callback with the device power state.
*/
POWER_STATE powerState;
ASSERT(!parentFdoExt->currentSetPowerIrp);
parentFdoExt->currentSetPowerIrp = irp;
if (systemState == PowerSystemWorking) {
powerState.DeviceState = PowerDeviceD0;
} else if (parentFdoExt->isWaitWakePending) {
powerState.DeviceState = parentFdoExt->deviceCapabilities.DeviceState[systemState];
ASSERT(PowerDeviceUnspecified != powerState.DeviceState);
} else {
powerState.DeviceState = PowerDeviceD3;
}
IoMarkIrpPending(irp);
status = irp->IoStatus.Status = STATUS_PENDING;
PoRequestPowerIrp( parentFdoExt->pdo,
IRP_MN_SET_POWER,
powerState,
ParentPowerRequestCompletion,
parentFdoExt, // context
NULL);
/*
* We want to complete the system-state power Irp
* with the result of the device-state power Irp.
* We'll complete the system-state power Irp when
* the device-state power Irp completes.
*
* Note: this may have ALREADY happened, so don't
* touch the original Irp anymore.
*/
justReturnPending = TRUE;
}
else {
/*
* For the remaining system power states,
* just pass down the IRP.
*/
}
}
break;
case DevicePowerState:
switch (irpSp->Parameters.Power.State.DeviceState) {
case PowerDeviceD0:
/*
* Resume from APM Suspend
*
* Do nothing here; Send down the read IRPs in the
* completion routine for this (the power) IRP.
*/
break;
case PowerDeviceD1:
case PowerDeviceD2:
case PowerDeviceD3:
/*
* Suspend
*/
if (parentFdoExt->state == STATE_STARTED){
parentFdoExt->state = STATE_SUSPENDED;
}
break;
}
break;
}
break;
case IRP_MN_WAIT_WAKE:
/*
* This is the WaitWake IRP that we requested for ourselves
* via PoRequestPowerIrp. Send it down to the parent,
* but record it in case we have to cancel it later.
*/
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
ASSERT(parentFdoExt->isWaitWakePending);
ASSERT(!parentFdoExt->parentWaitWakeIrp);
parentFdoExt->parentWaitWakeIrp = irp;
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
break;
}
}
if (!justReturnPending){
/*
* Whether we are completing or relaying this power IRP,
* we must call PoStartNextPowerIrp on Windows NT.
*/
PoStartNextPowerIrp(irp);
/*
* If this is a call for a collection-PDO, we complete it ourselves here.
* Otherwise, we pass it to the minidriver stack for more processing.
*/
if (completeIrpHere){
ASSERT(status != NO_STATUS);
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
else {
/*
* Call the parent driver with this Irp.
*/
IoCopyCurrentIrpStackLocationToNext(irp);
IoSetCompletionRoutine(irp, ParentPdoPowerCompletion, (PVOID)parentFdoExt, TRUE, TRUE, TRUE);
status = PoCallDriver(parentFdoExt->topDevObj, irp);
}
}
return status;
}
NTSTATUS ParentResetOrCyclePort(PPARENT_FDO_EXT parentFdoExt, PIRP irp, ULONG ioControlCode)
{
NTSTATUS status;
KIRQL oldIrql;
BOOLEAN proceed;
PBOOLEAN actionInProgress;
PLIST_ENTRY pendingIrpQueue;
if (ioControlCode == IOCTL_INTERNAL_USB_CYCLE_PORT) {
actionInProgress = &parentFdoExt->cyclePortInProgress;
pendingIrpQueue = &parentFdoExt->pendingCyclePortIrpQueue;
} else {
/*
* IOCTL_INTERNAL_USB_RESET_PORT
*/
actionInProgress = &parentFdoExt->resetPortInProgress;
pendingIrpQueue = &parentFdoExt->pendingResetPortIrpQueue;
}
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
if (*actionInProgress){
/*
* This is an overlapped RESET or CYCLE irp on the same parent.
* Queue the irp and return pending; we'll complete it
* _AFTER_ the first reset irp completes.
* (No need for a cancel routine here since RESET is quick).
*/
DBGWARN(("ParentInternalDeviceControl: queuing overlapping reset/cycle port call on parent"));
/*
* Need to mark the IRP pending if we are returning STATUS_PENDING.
* Failure to do so results in the IRP's completion routine not
* being called when the IRP is later completed asynchronously,
* and this results in a system hang if there is a thread waiting
* on that completion routine.
*/
IoMarkIrpPending(irp);
status = STATUS_PENDING;
InsertTailList(pendingIrpQueue, &irp->Tail.Overlay.ListEntry);
proceed = FALSE;
}
else {
*actionInProgress = TRUE;
proceed = TRUE;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (proceed){
LIST_ENTRY irpsToComplete;
PLIST_ENTRY listEntry;
IoCopyCurrentIrpStackLocationToNext(irp);
status = CallNextDriverSync(parentFdoExt, irp);
/*
* Some redundant RESET or CYCLE irps may have been sent while we
* were processing this one, and gotten queued.
* We'll complete these now that the parent has been reset.
*/
InitializeListHead(&irpsToComplete);
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
ASSERT(*actionInProgress);
*actionInProgress = FALSE;
/*
* Move the irps to a local queue with spinlock held.
* Then complete them after dropping the spinlock.
*/
while (!IsListEmpty(pendingIrpQueue)){
listEntry = RemoveHeadList(pendingIrpQueue);
InsertTailList(&irpsToComplete, listEntry);
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
/*
* Complete the dequeued irps _after_ dropping the spinlock.
*/
while (!IsListEmpty(&irpsToComplete)){
PIRP dequeuedIrp;
listEntry = RemoveHeadList(&irpsToComplete);
dequeuedIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
dequeuedIrp->IoStatus.Status = status;
IoCompleteRequest(dequeuedIrp, IO_NO_INCREMENT);
}
}
return status;
}
NTSTATUS ParentDeviceControl(PPARENT_FDO_EXT parentFdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status = NO_STATUS;
if (parentFdoExt->state == STATE_SUSPENDED ||
parentFdoExt->pendingIdleIrp) {
ParentSetD0(parentFdoExt);
}
switch (ioControlCode){
case IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER:
if (parentFdoExt->haveCSInterface){
status = GetMediaSerialNumber(parentFdoExt, irp);
}
else {
DBGWARN(("ParentDeviceControl - passing IOCTL_STORAGE_GET_MEDIA_SERIAL_NUMBER to parent because no Content Security interface on device"));
}
break;
}
if (status == NO_STATUS){
IoSkipCurrentIrpStackLocation(irp);
status = IoCallDriver(parentFdoExt->topDevObj, irp);
}
else {
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
return status;
}
NTSTATUS ParentInternalDeviceControl(PPARENT_FDO_EXT parentFdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
PURB urb;
NTSTATUS status = NO_STATUS;
switch (ioControlCode){
case IOCTL_INTERNAL_USB_RESET_PORT:
case IOCTL_INTERNAL_USB_CYCLE_PORT:
if (parentFdoExt->state == STATE_STARTED){
status = ParentResetOrCyclePort(parentFdoExt, irp, ioControlCode);
}
else {
DBGERR(("ParentInternalDeviceControl (IOCTL_INTERNAL_USB_RESET_PORT): BAD PNP state! - parent has state %xh.", parentFdoExt->state));
status = STATUS_DEVICE_NOT_READY;
}
break;
case IOCTL_INTERNAL_USB_SUBMIT_URB:
urb = irpSp->Parameters.Others.Argument1;
ASSERT(urb);
DBG_LOG_URB(urb);
if (parentFdoExt->state == STATE_STARTED){
/*
* Send the URB down to the parent.
* It's ok to not synchronize URB_FUNCTION_ABORT_PIPE
* and URB_FUNCTION_RESET_PIPE because they only effect
* the resources of one function.
*/
}
else {
DBGERR(("ParentInternalDeviceControl (abort/reset): BAD PNP state! - parent has state %xh.", parentFdoExt->state));
status = STATUS_DEVICE_NOT_READY;
}
break;
}
if (status == NO_STATUS){
/*
* Pass this irp to the parent driver.
*/
IoSkipCurrentIrpStackLocation(irp);
status = IoCallDriver(parentFdoExt->topDevObj, irp);
}
else if (status == STATUS_PENDING){
}
else {
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
return status;
}
VOID ParentIdleNotificationCallback(PPARENT_FDO_EXT parentFdoExt)
{
PIRP idleIrp;
PIRP parentIdleIrpToCancel = FALSE;
KIRQL oldIrql;
POWER_STATE powerState;
NTSTATUS ntStatus;
ULONG i;
BOOLEAN bIdleOk = TRUE;
DBGVERBOSE(("Parent %x going idle!", parentFdoExt));
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
ASSERT(parentFdoExt->deviceRelations);
for (i = 0; i < parentFdoExt->deviceRelations->Count; i++){
PDEVICE_OBJECT devObj = parentFdoExt->deviceRelations->Objects[i];
PDEVEXT devExt;
PFUNCTION_PDO_EXT thisFuncPdoExt;
ASSERT(devObj);
devExt = devObj->DeviceExtension;
ASSERT(devExt);
ASSERT(devExt->signature == USBCCGP_TAG);
ASSERT(!devExt->isParentFdo);
thisFuncPdoExt = &devExt->functionPdoExt;
idleIrp = thisFuncPdoExt->idleNotificationIrp;
ASSERT(idleIrp);
if (idleIrp) {
PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;
idleCallbackInfo = (PUSB_IDLE_CALLBACK_INFO)
IoGetCurrentIrpStackLocation(idleIrp)->\
Parameters.DeviceIoControl.Type3InputBuffer;
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.
DBGVERBOSE(("ParentIdleNotificationCallback: Calling driver's idle callback routine! %x %x",
idleCallbackInfo, idleCallbackInfo->IdleCallback));
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
idleCallbackInfo->IdleCallback(idleCallbackInfo->IdleContext);
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
// Be sure that the child actually powered down.
// Abort if the child aborted.
if (thisFuncPdoExt->state != STATE_SUSPENDED) {
bIdleOk = FALSE;
break;
}
} else {
// No callback
bIdleOk = FALSE;
break;
}
} else {
// No Idle IRP
bIdleOk = FALSE;
break;
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (bIdleOk) {
// If all the child function PDOs have been powered down,
// it is time to power down the parent.
powerState.DeviceState = PowerDeviceD2; // DeviceWake
PoRequestPowerIrp(parentFdoExt->topDevObj,
IRP_MN_SET_POWER,
powerState,
NULL,
NULL,
NULL);
} else {
// One or more of the child function 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 parent.
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
if (parentFdoExt->pendingIdleIrp){
parentIdleIrpToCancel = parentFdoExt->pendingIdleIrp;
parentFdoExt->pendingIdleIrp = NULL;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (parentIdleIrpToCancel){
IoCancelIrp(parentIdleIrpToCancel);
}
}
}
NTSTATUS ParentIdleNotificationRequestComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PPARENT_FDO_EXT parentFdoExt)
{
NTSTATUS ntStatus;
KIRQL oldIrql;
//
// DeviceObject is NULL because we sent the irp
//
UNREFERENCED_PARAMETER(DeviceObject);
DBGVERBOSE(("Idle notification IRP for parent %x completed %x\n", parentFdoExt, Irp->IoStatus.Status));
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
parentFdoExt->pendingIdleIrp = NULL;
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
ntStatus = Irp->IoStatus.Status;
/*
* If parent Idle IRP failed, fail all function Idle IRPs.
*/
if (!NT_SUCCESS(ntStatus)){
if (parentFdoExt->state == STATE_SUSPENDED ||
parentFdoExt->pendingIdleIrp) {
ParentSetD0(parentFdoExt);
}
CompleteAllFunctionIdleIrps(parentFdoExt, ntStatus);
}
/* 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.
*/
IoFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS SubmitParentIdleRequestIrp(PPARENT_FDO_EXT parentFdoExt)
{
PIRP irp = NULL;
PIO_STACK_LOCATION nextStack;
NTSTATUS ntStatus;
KIRQL oldIrql;
DBGVERBOSE(("SubmitParentIdleRequestIrp %x", parentFdoExt));
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
if (parentFdoExt->pendingIdleIrp){
ntStatus = STATUS_DEVICE_BUSY;
}
else {
parentFdoExt->idleCallbackInfo.IdleCallback = ParentIdleNotificationCallback;
parentFdoExt->idleCallbackInfo.IdleContext = (PVOID)parentFdoExt;
irp = IoAllocateIrp(parentFdoExt->pdo->StackSize, FALSE);
if (irp){
/*
* Set pendingIdleIrp with lock held so that we don't
* send down more than one.
* Then send this one down after dropping the lock.
*/
parentFdoExt->pendingIdleIrp = irp;
}
else {
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (irp){
nextStack = IoGetNextIrpStackLocation(irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;
nextStack->Parameters.DeviceIoControl.Type3InputBuffer = &parentFdoExt->idleCallbackInfo;
nextStack->Parameters.DeviceIoControl.InputBufferLength = sizeof(struct _USB_IDLE_CALLBACK_INFO);
IoSetCompletionRoutine(irp,
ParentIdleNotificationRequestComplete,
parentFdoExt,
TRUE,
TRUE,
TRUE);
ntStatus = IoCallDriver(parentFdoExt->topDevObj, irp);
ASSERT(ntStatus == STATUS_PENDING);
}
return ntStatus;
}
/*
* CheckParentIdle
*
*
* This function determines if a composite device is ready to be idled out,
* and does so if ready.
*
*/
VOID CheckParentIdle(PPARENT_FDO_EXT parentFdoExt)
{
PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;
KIRQL oldIrql;
BOOLEAN bAllIdle;
ULONG i;
DBGVERBOSE(("Check Parent Idle %x", parentFdoExt));
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
bAllIdle = TRUE; // Assume that everyone wants to idle.
ASSERT(parentFdoExt->deviceRelations);
for (i = 0; i < parentFdoExt->deviceRelations->Count; i++) {
PDEVICE_OBJECT devObj = parentFdoExt->deviceRelations->Objects[i];
PDEVEXT devExt;
PFUNCTION_PDO_EXT thisFuncPdoExt;
ASSERT(devObj);
devExt = devObj->DeviceExtension;
ASSERT(devExt);
ASSERT(devExt->signature == USBCCGP_TAG);
ASSERT(!devExt->isParentFdo);
thisFuncPdoExt = &devExt->functionPdoExt;
if (!thisFuncPdoExt->idleNotificationIrp){
bAllIdle = FALSE;
break;
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
/*
* If all functions have received an idle request,
* then submit an idle request for the parent.
*/
if (bAllIdle ) {
DBGVERBOSE(("CheckParentIdle: All function PDOs on parent %x idle!", parentFdoExt));
SubmitParentIdleRequestIrp(parentFdoExt);
}
}
NTSTATUS SubmitParentWaitWakeIrp(PPARENT_FDO_EXT parentFdoExt)
{
NTSTATUS status;
POWER_STATE powerState;
PIRP dummyIrp;
ASSERT(parentFdoExt->isWaitWakePending);
powerState.SystemState = PowerSystemWorking;
status = PoRequestPowerIrp( parentFdoExt->topDevObj,
IRP_MN_WAIT_WAKE,
powerState,
ParentWaitWakeComplete,
parentFdoExt, // context
&dummyIrp);
ASSERT(NT_SUCCESS(status));
return status;
}
/*
********************************************************************************
* ParentWaitWakePowerRequestCompletion
********************************************************************************
*
*
*/
NTSTATUS ParentWaitWakePowerRequestCompletion(
IN PDEVICE_OBJECT devObj,
IN UCHAR minorFunction,
IN POWER_STATE powerState,
IN PVOID context,
IN PIO_STATUS_BLOCK ioStatus)
{
PPARENT_FDO_EXT parentFdoExt = (PPARENT_FDO_EXT)context;
NTSTATUS status;
status = ioStatus->Status;
CompleteAllFunctionWaitWakeIrps(parentFdoExt, STATUS_SUCCESS);
return status;
}
/*
********************************************************************************
* ParentWaitWakeComplete
********************************************************************************
*
*/
NTSTATUS ParentWaitWakeComplete(
IN PDEVICE_OBJECT deviceObject,
IN UCHAR minorFunction,
IN POWER_STATE powerState,
IN PVOID context,
IN PIO_STATUS_BLOCK ioStatus)
{
PPARENT_FDO_EXT parentFdoExt = (PPARENT_FDO_EXT)context;
NTSTATUS status;
KIRQL oldIrql;
POWER_STATE pwrState;
status = ioStatus->Status;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
ASSERT(parentFdoExt->isWaitWakePending);
parentFdoExt->isWaitWakePending = FALSE;
parentFdoExt->parentWaitWakeIrp = NULL;
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (NT_SUCCESS(status) && (parentFdoExt->state == STATE_SUSPENDED)){
/*
* Per the DDK: if parent is suspended,
* do not complete the function PDOs' WaitWake irps here;
* wait for the parent to get the D0 irp.
*/
pwrState.DeviceState = PowerDeviceD0;
PoRequestPowerIrp( parentFdoExt->pdo,
IRP_MN_SET_POWER,
pwrState,
ParentWaitWakePowerRequestCompletion,
parentFdoExt, // context
NULL);
}
else {
CompleteAllFunctionWaitWakeIrps(parentFdoExt, status);
}
return STATUS_SUCCESS;
}
/*
********************************************************************************
* ParentSetD0Completion
********************************************************************************
*
*/
NTSTATUS ParentSetD0Completion(
IN PDEVICE_OBJECT DeviceObject,
IN UCHAR MinorFunction,
IN POWER_STATE PowerState,
IN PVOID Context,
IN PIO_STATUS_BLOCK IoStatus
)
{
NTSTATUS ntStatus;
PKEVENT pEvent = Context;
KeSetEvent(pEvent, 1, FALSE);
ntStatus = IoStatus->Status;
return ntStatus;
}
/*
********************************************************************************
* ParentSetD0
********************************************************************************
*
*/
NTSTATUS ParentSetD0(IN PPARENT_FDO_EXT parentFdoExt)
{
KEVENT event;
POWER_STATE powerState;
NTSTATUS ntStatus;
PAGED_CODE();
DBGVERBOSE(("ParentSetD0, power up devext %x\n", parentFdoExt));
KeInitializeEvent(&event, NotificationEvent, FALSE);
powerState.DeviceState = PowerDeviceD0;
// Power up the device.
ntStatus = PoRequestPowerIrp(parentFdoExt->topDevObj,
IRP_MN_SET_POWER,
powerState,
ParentSetD0Completion,
&event,
NULL);
ASSERT(ntStatus == STATUS_PENDING);
if (ntStatus == STATUS_PENDING) {
ntStatus = KeWaitForSingleObject(&event,
Suspended,
KernelMode,
FALSE,
NULL);
}
return ntStatus;
}