mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1627 lines
52 KiB
1627 lines
52 KiB
/*
|
|
*************************************************************************
|
|
* 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;
|
|
}
|
|
|
|
|