Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

1982 lines
63 KiB

/*
*************************************************************************
* File: FUNCTION.C
*
* Module: USBCCGP.SYS
* USB Common Class Generic Parent driver.
*
* Copyright (c) 1998 Microsoft Corporation
*
*
* Author: ervinp
*
*************************************************************************
*/
#include <wdm.h>
#include <stdio.h>
#include <usbdi.h>
#include <usbdlib.h>
#include <usbioctl.h>
#include "usbccgp.h"
#include "security.h"
#include "debug.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, CreateStaticFunctionPDOs)
#pragma alloc_text(PAGE, BuildCompatibleIDs)
#pragma alloc_text(PAGE, QueryFunctionPdoID)
#pragma alloc_text(PAGE, QueryFunctionDeviceRelations)
#pragma alloc_text(PAGE, QueryFunctionCapabilities)
#pragma alloc_text(PAGE, HandleFunctionPdoPower)
#pragma alloc_text(PAGE, FreeFunctionPDOResources)
#pragma alloc_text(PAGE, InstallExtPropDesc)
#pragma alloc_text(PAGE, InstallExtPropDescSections)
#endif
/*
* CreateStaticFunctionPDOs
*
*
* Create a PDO for each function on the device.
* Treat each interface as a function (with exceptions for audio).
*/
NTSTATUS CreateStaticFunctionPDOs(PPARENT_FDO_EXT parentFdoExt)
{
NTSTATUS status;
PUSB_CONFIGURATION_DESCRIPTOR configDesc;
ULONG numFuncIfaces;
ULONG i;
PAGED_CODE();
configDesc = parentFdoExt->selectedConfigDesc;
ASSERT(configDesc);
ASSERT(configDesc->bNumInterfaces > 0);
ASSERT(!parentFdoExt->deviceRelations);
ASSERT(parentFdoExt->interfaceList);
/*
* See if there is a Content Security Interface and if so initialize.
* (There should be at most one CS interface).
*/
for (i = 0; i < configDesc->bNumInterfaces; i++){
UCHAR ifaceClass = parentFdoExt->interfaceList[i].InterfaceDescriptor->bInterfaceClass;
if (ifaceClass == USB_DEVICE_CLASS_CONTENT_SECURITY){
ULONG ifaceNum = parentFdoExt->interfaceList[i].InterfaceDescriptor->bInterfaceNumber;
DBGVERBOSE(("Found CS interface #%d.", ifaceNum));
ASSERT(!parentFdoExt->haveCSInterface);
InitCSInfo(parentFdoExt, ifaceNum);
ASSERT(parentFdoExt->haveCSInterface);
}
}
/*
* The configuration descriptor contains a series of interfaces,
* which are grouped into some number of "functions".
* Count the number of "function" interface groupings.
*/
if (ISPTR(parentFdoExt->msExtConfigDesc))
{
parentFdoExt->numFunctions = parentFdoExt->msExtConfigDesc->Header.bCount;
}
else
{
for (i = 0; GetFunctionInterfaceListBase(parentFdoExt, i, &numFuncIfaces); i++);
parentFdoExt->numFunctions = i;
}
ASSERT(parentFdoExt->numFunctions);
DBGVERBOSE((" Device has %d interfaces and %d functions", (ULONG)configDesc->bNumInterfaces, parentFdoExt->numFunctions));
/*
* Allocate a PDO for each function
*/
parentFdoExt->deviceRelations =
ALLOCPOOL( NonPagedPool,
sizeof(DEVICE_RELATIONS) + (parentFdoExt->numFunctions *
sizeof(PDEVICE_OBJECT)));
if (parentFdoExt->deviceRelations){
for (i = 0; i < parentFdoExt->numFunctions; i++){
PDEVICE_OBJECT functionPdo = NULL;
status = IoCreateDevice(parentFdoExt->driverObj,
sizeof(DEVEXT), // Device Extension size
NULL, // device name
FILE_DEVICE_UNKNOWN,
FILE_AUTOGENERATED_DEVICE_NAME,// Device Chars
FALSE,
&functionPdo);
if (NT_SUCCESS(status)){
PDEVEXT devExt;
PFUNCTION_PDO_EXT functionPdoExt;
ASSERT(functionPdo);
devExt = functionPdo->DeviceExtension;
RtlZeroMemory(devExt, sizeof(DEVEXT));
devExt->signature = USBCCGP_TAG;
devExt->isParentFdo = FALSE;
functionPdo->Flags |= DO_POWER_PAGABLE;
functionPdoExt = &devExt->functionPdoExt;
functionPdoExt->functionIndex = i;
functionPdoExt->pdo = functionPdo;
functionPdoExt->parentFdoExt = parentFdoExt;
functionPdoExt->idleNotificationIrp = NULL;
KeInitializeSpinLock(&functionPdoExt->functionPdoExtSpinLock);
/*
* The parent's config descriptor contains a list
* of interface descriptors, sorted by function.
* Get a pointer to the first interface descriptor
* for this function.
*/
if (ISPTR(parentFdoExt->msExtConfigDesc))
{
functionPdoExt->baseInterfaceNumber =
parentFdoExt->msExtConfigDesc->Function[i].bFirstInterfaceNumber;
functionPdoExt->numInterfaces =
parentFdoExt->msExtConfigDesc->Function[i].bInterfaceCount;
functionPdoExt->functionInterfaceList =
&parentFdoExt->interfaceList[functionPdoExt->baseInterfaceNumber];
}
else
{
functionPdoExt->functionInterfaceList = GetFunctionInterfaceListBase(parentFdoExt, i, &numFuncIfaces);
functionPdoExt->numInterfaces = numFuncIfaces;
ASSERT(functionPdoExt->functionInterfaceList);
functionPdoExt->baseInterfaceNumber = functionPdoExt->functionInterfaceList[0].InterfaceDescriptor->bInterfaceNumber;
}
/*
* Create the device descriptor that clients see for this function.
* This is the same as the parent's device descriptor except:
* if the first interface of this function has an iInterface string
* descriptor, then we substitute the parent device descriptor's iProduct
* string with the iInterface string. That way, the client's view of
* the device will only represent the interfaces in that function.
*/
RtlCopyMemory( &functionPdoExt->functionDeviceDesc,
&parentFdoExt->deviceDesc,
sizeof(USB_DEVICE_DESCRIPTOR));
ASSERT(functionPdoExt->numInterfaces > 0);
if (functionPdoExt->functionInterfaceList[0].InterfaceDescriptor->iInterface){
functionPdoExt->functionDeviceDesc.iProduct =
functionPdoExt->functionInterfaceList[0].InterfaceDescriptor->iInterface;
}
parentFdoExt->deviceRelations->Objects[i] = functionPdo;
/*
* We may pass IRPs from the function PDO to the
* parent FDO. Since we are not calling
* IoAttachDeviceToDeviceStack to physically
* attach this function PDO to a device stack,
* we must set the "height" of this PDO ourselves,
* so that any IRPs sent to this PDO have enough
* IRP stack locations to go all the way down
* the parent FDO's stack.
*/
functionPdo->StackSize = parentFdoExt->fdo->StackSize+1;
DBGVERBOSE(("Created function PDO %p (#%d)", (ULONG_PTR)functionPdo, i));
}
else {
break;
}
}
if (i == parentFdoExt->numFunctions){
parentFdoExt->deviceRelations->Count = parentFdoExt->numFunctions;
status = STATUS_SUCCESS;
}
else {
FREEPOOL(parentFdoExt->deviceRelations);
parentFdoExt->deviceRelations = NULL;
}
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(NT_SUCCESS(status));
return status;
}
#define NibbleToHexW( byte ) (NibbleW[byte])
WCHAR NibbleW[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
typedef struct _CLASS_COMAPTIBLE_IDS
{
// L"USB\\Class_nn&SubClass_nn&Prot_nn\0"
//
WCHAR ClassStr1[sizeof(L"USB\\Class_")/sizeof(WCHAR)-1];
WCHAR ClassHex1[2];
WCHAR SubClassStr1[sizeof(L"&SubClass_")/sizeof(WCHAR)-1];
WCHAR SubClassHex1[2];
WCHAR Prot1[sizeof(L"&Prot_")/sizeof(WCHAR)-1];
WCHAR ProtHex1[2];
WCHAR Null1[1];
// L"USB\\Class_nn&SubClass_nn\0"
//
WCHAR ClassStr2[sizeof(L"USB\\Class_")/sizeof(WCHAR)-1];
WCHAR ClassHex2[2];
WCHAR SubClassStr2[sizeof(L"&SubClass_")/sizeof(WCHAR)-1];
WCHAR SubClassHex2[2];
WCHAR Null2[1];
// L"USB\\Class_nn&SubClass_nn\0"
//
WCHAR ClassStr3[sizeof(L"USB\\Class_")/sizeof(WCHAR)-1];
WCHAR ClassHex3[2];
WCHAR Null3[1];
WCHAR DoubleNull[1];
} CLASS_COMAPTIBLE_IDS, *PCLASS_COMAPTIBLE_IDS;
static CLASS_COMAPTIBLE_IDS ClassCompatibleIDs =
{
// L"USB\\Class_nn&SubClass_nn&Prot_nn\0"
//
{'U','S','B','\\','C','l','a','s','s','_'},
{'n','n'},
{'&','S','u','b','C','l','a','s','s','_'},
{'n','n'},
{'&','P','r','o','t','_'},
{'n','n'},
{0},
// L"USB\\Class_nn&SubClass_nn\0"
//
{'U','S','B','\\','C','l','a','s','s','_'},
{'n','n'},
{'&','S','u','b','C','l','a','s','s','_'},
{'n','n'},
{0},
// L"USB\\Class_nn\0"
//
{'U','S','B','\\','C','l','a','s','s','_'},
{'n','n'},
{0},
{0}
};
PWCHAR
BuildCompatibleIDs(
IN PUCHAR CompatibleID,
IN PUCHAR SubCompatibleID,
IN UCHAR Class,
IN UCHAR SubClass,
IN UCHAR Protocol
)
{
ULONG ulTotal;
PWCHAR pwch;
PWCHAR pwchTmp;
PCLASS_COMAPTIBLE_IDS pClassIds;
ULONG i;
WCHAR ClassHi = NibbleToHexW((Class) >> 4);
WCHAR ClassLo = NibbleToHexW((Class) & 0x0f);
WCHAR SubClassHi = NibbleToHexW((SubClass) >> 4);
WCHAR SubClassLo = NibbleToHexW((SubClass) & 0x0f);
WCHAR ProtocolHi = NibbleToHexW((Protocol) >> 4);
WCHAR ProtocolLo = NibbleToHexW((Protocol) & 0x0f);
PAGED_CODE();
ulTotal = sizeof(CLASS_COMAPTIBLE_IDS);
if (SubCompatibleID && SubCompatibleID[0] != 0)
{
ulTotal += sizeof(L"USB\\MS_COMP_xxxxxxxx&MS_SUBCOMP_xxxxxxxx");
}
if (CompatibleID && CompatibleID[0] != 0)
{
ulTotal += sizeof(L"USB\\MS_COMP_xxxxxxxx");
}
pwch = ALLOCPOOL(PagedPool, ulTotal);
if (pwch)
{
pwchTmp = pwch;
if (SubCompatibleID && SubCompatibleID[0] != 0)
{
RtlCopyMemory(pwchTmp,
L"USB\\MS_COMP_",
sizeof(L"USB\\MS_COMP_")-sizeof(WCHAR));
(PUCHAR)pwchTmp += sizeof(L"USB\\MS_COMP_")-sizeof(WCHAR);
for (i = 0; i < 8 && CompatibleID[i] != 0; i++)
{
*pwchTmp++ = (WCHAR)CompatibleID[i];
}
RtlCopyMemory(pwchTmp,
L"&MS_SUBCOMP_",
sizeof(L"&MS_SUBCOMP_")-sizeof(WCHAR));
(PUCHAR)pwchTmp += sizeof(L"&MS_SUBCOMP_")-sizeof(WCHAR);
for (i = 0; i < 8 && SubCompatibleID[i] != 0; i++)
{
*pwchTmp++ = (WCHAR)SubCompatibleID[i];
}
*pwchTmp++ = '\0';
}
if (CompatibleID && CompatibleID[0] != 0)
{
RtlCopyMemory(pwchTmp,
L"USB\\MS_COMP_",
sizeof(L"USB\\MS_COMP_")-sizeof(WCHAR));
(PUCHAR)pwchTmp += sizeof(L"USB\\MS_COMP_")-sizeof(WCHAR);
for (i = 0; i < 8 && CompatibleID[i] != 0; i++)
{
*pwchTmp++ = (WCHAR)CompatibleID[i];
}
*pwchTmp++ = '\0';
}
pClassIds = (PCLASS_COMAPTIBLE_IDS)pwchTmp;
// Copy over the constant set of strings:
// L"USB\\Class_nn&SubClass_nn&Prot_nn\0"
// L"USB\\Class_nn&SubClass_nn\0"
// L"USB\\Class_nn\0"
//
RtlCopyMemory(pClassIds,
&ClassCompatibleIDs,
sizeof(CLASS_COMAPTIBLE_IDS));
// Fill in the 'nn' blanks
//
pClassIds->ClassHex1[0] =
pClassIds->ClassHex2[0] =
pClassIds->ClassHex3[0] = ClassHi;
pClassIds->ClassHex1[1] =
pClassIds->ClassHex2[1] =
pClassIds->ClassHex3[1] = ClassLo;
pClassIds->SubClassHex1[0] =
pClassIds->SubClassHex2[0] = SubClassHi;
pClassIds->SubClassHex1[1] =
pClassIds->SubClassHex2[1] = SubClassLo;
pClassIds->ProtHex1[0] = ProtocolHi;
pClassIds->ProtHex1[1] = ProtocolLo;
}
return pwch;
}
/*
********************************************************************************
* QueryFunctionPdoID
********************************************************************************
*
*
*
*/
NTSTATUS QueryFunctionPdoID(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp;
NTSTATUS status;
PAGED_CODE();
irpSp = IoGetCurrentIrpStackLocation(irp);
switch (irpSp->Parameters.QueryId.IdType){
case BusQueryHardwareIDs:
/*
* Call down the parent FDO's stack to get a multi-string of hardware ids for the PDO.
*/
IoCopyCurrentIrpStackLocationToNext(irp);
status = CallDriverSync(functionPdoExt->parentFdoExt->fdo, irp);
if (NT_SUCCESS(status)){
PWCHAR oldIDs, newIDs;
/*
* Append '&MI_xx' to each hardware id in the multi-string,
* where 'xx' is the function number.
*/
oldIDs = (PWCHAR)irp->IoStatus.Information;
irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
newIDs = AppendInterfaceNumber(oldIDs, functionPdoExt->baseInterfaceNumber);
ExFreePool(oldIDs);
if (newIDs){
irp->IoStatus.Information = (ULONG_PTR)newIDs;
status = STATUS_SUCCESS;
}
else {
irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
status = STATUS_UNSUCCESSFUL;
}
}
ASSERT(NT_SUCCESS(status));
break;
case BusQueryDeviceID:
/*
* Call down the parent FDO's stack to get a the device id for the device's PDO.
*/
IoCopyCurrentIrpStackLocationToNext(irp);
status = CallDriverSync(functionPdoExt->parentFdoExt->fdo, irp);
if (NT_SUCCESS(status)){
PWCHAR oldId, newId, tmpId;
/*
* Append '&MI_xx' to the device id,
* where 'xx' is the function number.
*/
/*
* First make this string into a multi-string.
*/
oldId = (PWCHAR)irp->IoStatus.Information;
tmpId = ALLOCPOOL(PagedPool, (WStrLen(oldId)+2)*sizeof(WCHAR));
if (tmpId){
ULONG len = WStrCpy(tmpId, oldId);
/*
* Add the extra NULL to terminate the multi-string.
*/
tmpId[len+1] = UNICODE_NULL;
/*
* Append the function-number ending '&MI_xx' .
*/
newId = AppendInterfaceNumber(tmpId, functionPdoExt->baseInterfaceNumber);
if (newId){
irp->IoStatus.Information = (ULONG_PTR)newId;
}
else {
status = STATUS_DEVICE_DATA_ERROR;
irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
}
ExFreePool(tmpId);
}
else {
status = STATUS_DEVICE_DATA_ERROR;
irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
}
ExFreePool(oldId);
}
ASSERT(NT_SUCCESS(status));
break;
case BusQueryInstanceID:
/*
* Produce an instance-id for this function-PDO.
*
* Note: NTKERN frees the returned pointer, so we must provide a fresh pointer.
*/
{
PWSTR instanceId = MemDup(L"0000", sizeof(L"0000"));
if (instanceId){
swprintf(instanceId, L"%04x", functionPdoExt->baseInterfaceNumber);
irp->IoStatus.Information = (ULONG_PTR)instanceId;
status = STATUS_SUCCESS;
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
ASSERT(NT_SUCCESS(status));
break;
case BusQueryCompatibleIDs:
{
PUSB_INTERFACE_DESCRIPTOR ifaceDesc;
PUCHAR compatibleID;
PUCHAR subCompatibleID;
/*
* Build the MS Extended Compatible ID strings.
*/
if (ISPTR(functionPdoExt->parentFdoExt->msExtConfigDesc))
{
PMS_EXT_CONFIG_DESC msExtConfigDesc;
ULONG i;
msExtConfigDesc = functionPdoExt->parentFdoExt->msExtConfigDesc;
i = functionPdoExt->functionIndex;
ASSERT(i < msExtConfigDesc->Header.bCount);
compatibleID = msExtConfigDesc->Function[i].CompatibleID;
subCompatibleID = msExtConfigDesc->Function[i].SubCompatibleID;
}
else
{
compatibleID = NULL;
subCompatibleID = NULL;
}
ifaceDesc = functionPdoExt->functionInterfaceList->InterfaceDescriptor;
ASSERT(ifaceDesc);
irp->IoStatus.Information = (ULONG_PTR)
BuildCompatibleIDs(compatibleID,
subCompatibleID,
ifaceDesc->bInterfaceClass,
ifaceDesc->bInterfaceSubClass,
ifaceDesc->bInterfaceProtocol);
if (irp->IoStatus.Information)
{
status = STATUS_SUCCESS;
}
else
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
break;
default:
/*
* Do not return STATUS_NOT_SUPPORTED;
* keep the default status
* (this allows filter drivers to work).
*/
status = irp->IoStatus.Status;
break;
}
return status;
}
/*
********************************************************************************
* QueryFunctionDeviceRelations
********************************************************************************
*
*
*/
NTSTATUS QueryFunctionDeviceRelations(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp;
NTSTATUS status;
PAGED_CODE();
irpSp = IoGetCurrentIrpStackLocation(irp);
if (irpSp->Parameters.QueryDeviceRelations.Type == TargetDeviceRelation){
/*
* Return a reference to this PDO
*/
PDEVICE_RELATIONS devRel = ALLOCPOOL(PagedPool, sizeof(DEVICE_RELATIONS));
if (devRel){
/*
* Add a reference to the PDO, since CONFIGMG will free it.
*/
ObReferenceObject(functionPdoExt->pdo);
devRel->Objects[0] = functionPdoExt->pdo;
devRel->Count = 1;
irp->IoStatus.Information = (ULONG_PTR)devRel;
status = STATUS_SUCCESS;
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
else {
/*
* Fail this Irp by returning the default
* status (typically STATUS_NOT_SUPPORTED).
*/
status = irp->IoStatus.Status;
}
return status;
}
/*
********************************************************************************
* QueryFunctionCapabilities
********************************************************************************
*
*
*/
NTSTATUS QueryFunctionCapabilities(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
PDEVICE_CAPABILITIES deviceCapabilities = irpSp->Parameters.DeviceCapabilities.Capabilities;
PAGED_CODE();
/*
* Copy the parent device's capabilities,
* then set the flags we care about
*/
ASSERT(deviceCapabilities);
*deviceCapabilities = functionPdoExt->parentFdoExt->deviceCapabilities;
deviceCapabilities->Removable = TRUE;
deviceCapabilities->UniqueID = FALSE;
// SurpriseRemovalOK is FALSE by default, and some clients (NDIS)
// set it to true on the way down, in accordance with the DDK.
// Also, some clients (USBSTOR) leave it as the default, FALSE, and
// expect it to remain so.
// deviceCapabilities->SurpriseRemovalOK = TRUE;
deviceCapabilities->RawDeviceOK = FALSE;
return STATUS_SUCCESS;
}
/*
* HandleFunctionPdoPower
*
*
*/
NTSTATUS HandleFunctionPdoPower(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp;
BOOLEAN queuedIrp = FALSE;
BOOLEAN calledPoStartNextPowerIrp = FALSE;
NTSTATUS status;
PAGED_CODE();
irpSp = IoGetCurrentIrpStackLocation(irp);
switch (irpSp->MinorFunction){
case IRP_MN_SET_POWER:
switch (irpSp->Parameters.Power.Type) {
case SystemPowerState:
status = STATUS_SUCCESS;
break;
case DevicePowerState:
// If the parent has been selectively suspended, then
// power it up now to service this request.
if (functionPdoExt->parentFdoExt->state == STATE_SUSPENDED ||
functionPdoExt->parentFdoExt->pendingIdleIrp) {
ParentSetD0(functionPdoExt->parentFdoExt);
}
ASSERT(functionPdoExt->parentFdoExt->state != STATE_SUSPENDED);
switch (irpSp->Parameters.Power.State.DeviceState){
case PowerDeviceD0:
if (functionPdoExt->state == STATE_SUSPENDED){
functionPdoExt->state = STATE_STARTED;
}
CompleteFunctionIdleNotification(functionPdoExt);
status = STATUS_SUCCESS;
break;
case PowerDeviceD1:
case PowerDeviceD2:
case PowerDeviceD3:
/*
* Suspend
*/
if (functionPdoExt->state == STATE_STARTED){
functionPdoExt->state = STATE_SUSPENDED;
}
status = STATUS_SUCCESS;
break;
default:
/*
* Return the default status.
*/
status = irp->IoStatus.Status;
break;
}
break;
default:
/*
* Return the default status.
*/
status = irp->IoStatus.Status;
break;
}
break;
case IRP_MN_WAIT_WAKE:
/*
* We queue all WW irps on function PDO's and issue
* just one irp down to the parent.
*/
/*
* Call PoStartNextPowerIrp first since we can't
* touch the irp after queuing it.
*/
PoStartNextPowerIrp(irp);
calledPoStartNextPowerIrp = TRUE;
status = EnqueueFunctionWaitWakeIrp(functionPdoExt, irp);
if (status == STATUS_PENDING){
queuedIrp = TRUE;
}
break;
case IRP_MN_POWER_SEQUENCE:
TRAP("IRP_MN_POWER_SEQUENCE (coverage trap)");
status = irp->IoStatus.Status;
break;
case IRP_MN_QUERY_POWER:
/*
* We allow all power transitions
*/
status = STATUS_SUCCESS;
break;
default:
/*
* Return the default status;
*/
status = irp->IoStatus.Status;
break;
}
if (!calledPoStartNextPowerIrp){
PoStartNextPowerIrp(irp);
}
if (!queuedIrp){
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
return status;
}
/*
* BuildFunctionConfigurationDescriptor
*
*
* Note: this function cannot be pageable because internal
* ioctls may be sent at IRQL==DISPATCH_LEVEL.
*/
NTSTATUS BuildFunctionConfigurationDescriptor(
PFUNCTION_PDO_EXT functionPdoExt,
PUCHAR buffer,
ULONG bufferLength,
PULONG bytesReturned)
{
PUSB_CONFIGURATION_DESCRIPTOR parentConfigDesc;
PUSB_CONFIGURATION_DESCRIPTOR functionConfigDesc;
PUCHAR parentConfigDescEnd;
PUSB_COMMON_DESCRIPTOR commonDesc;
PUSB_INTERFACE_DESCRIPTOR thisIfaceDesc;
PUCHAR scratch;
ULONG totalLength;
NTSTATUS status;
ULONG i;
// BUGBUG - change this to use the USBD ParseConfiguration function
/*
* The function's configuration descriptor will include
* a subset of the interface descriptors in the parent's
* configuration descriptor.
*/
parentConfigDesc = functionPdoExt->parentFdoExt->selectedConfigDesc;
parentConfigDescEnd = (PUCHAR)((PUCHAR)parentConfigDesc + parentConfigDesc->wTotalLength);
/*
* First calculate the total length of what we'll be copying.
* It will include a configuration descriptor followed by
* some number of interface descriptors.
* Each interface descriptor may be followed by a some number
* of class-specific descriptors.
*/
totalLength = sizeof(USB_CONFIGURATION_DESCRIPTOR);
for (i = 0; i < functionPdoExt->numInterfaces; i++) {
/*
* We will copy the interface descriptor and all following
* descriptors until either the next interface
* descriptor or the end of the entire
* configuration descriptor.
*/
thisIfaceDesc = functionPdoExt->functionInterfaceList[i].InterfaceDescriptor;
ASSERT(thisIfaceDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE);
commonDesc = (PUSB_COMMON_DESCRIPTOR)thisIfaceDesc;
do {
totalLength += commonDesc->bLength;
commonDesc = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)commonDesc) + commonDesc->bLength);
}
while (((PUCHAR)commonDesc < parentConfigDescEnd) &&
((commonDesc->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE) ||
(((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->bInterfaceNumber == thisIfaceDesc->bInterfaceNumber)));
}
scratch = ALLOCPOOL(NonPagedPool, totalLength);
if (scratch){
PUCHAR pch;
pch = scratch;
RtlCopyMemory(pch, parentConfigDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
pch += sizeof(USB_CONFIGURATION_DESCRIPTOR);
for (i = 0; i < functionPdoExt->numInterfaces; i++) {
/*
* Copy the interface descriptor and all following
* descriptors until either the next interface
* descriptor or the end of the entire
* configuration descriptor.
*/
thisIfaceDesc = functionPdoExt->functionInterfaceList[i].InterfaceDescriptor;
ASSERT(thisIfaceDesc->bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE);
commonDesc = (PUSB_COMMON_DESCRIPTOR)thisIfaceDesc;
do {
RtlCopyMemory(pch, commonDesc, commonDesc->bLength);
pch += commonDesc->bLength;
commonDesc = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)commonDesc) + commonDesc->bLength);
}
while (((PUCHAR)commonDesc < parentConfigDescEnd) &&
((commonDesc->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE) ||
(((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->bInterfaceNumber == thisIfaceDesc->bInterfaceNumber)));
}
/*
* This 'function' child's config descriptor contains
* a subset of the parent's interface descriptors.
* Update the child's configuration descriptor's size
* to reflect the possibly-reduced number of interface descriptors.
*/
functionConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR)scratch;
functionConfigDesc->bNumInterfaces = (UCHAR)functionPdoExt->numInterfaces;
functionConfigDesc->wTotalLength = (USHORT)totalLength;
/*
* Copy as much of the config descriptor as will fit in the caller's buffer.
* Return success whether or not the caller's buffer was actually big enough (BUGBUG ? - this is what usbhub did).
*/
*bytesReturned = MIN(bufferLength, totalLength);
RtlCopyMemory(buffer, scratch, *bytesReturned);
DBGDUMPBYTES("BuildFunctionConfigurationDescriptor built config desc for function", buffer, *bytesReturned);
FREEPOOL(scratch);
status = STATUS_SUCCESS;
}
else {
ASSERT(scratch);
status = STATUS_INSUFFICIENT_RESOURCES;
*bytesReturned = 0;
}
return status;
}
#if DBG
/*
* FunctionIoctlCompletion
*
* Monitor URB completion status for DEBUG ONLY.
*/
NTSTATUS FunctionIoctlCompletion(IN PDEVICE_OBJECT devObj, IN PIRP irp, IN PVOID context)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status = irp->IoStatus.Status;
PURB urb;
PUCHAR urbFuncName;
switch (ioControlCode){
case IOCTL_INTERNAL_USB_SUBMIT_URB:
urb = irpSp->Parameters.Others.Argument1;
urbFuncName = DbgGetUrbName(urb->UrbHeader.Function);
if (!urbFuncName) urbFuncName = "???";
if (((status != STATUS_SUCCESS) && (status != STATUS_CANCELLED)) ||
!USBD_SUCCESS(urb->UrbHeader.Status)){
DBGVERBOSE(("FunctionIoctlCompletion: %s (%xh) returned ntstatus %xh, urbstatus %xh.", urbFuncName, urb->UrbHeader.Function, status, urb->UrbHeader.Status));
DBG_LOG_URB(urb);
DBGVERBOSE(("<>"));
}
break;
default:
if (status != STATUS_SUCCESS){
DBGWARN(("FunctionIoctlCompletion: ioctl %xh returned %xh.", ioControlCode, status));
}
break;
}
/*
* Must propagate the pending bit if a lower driver returned pending.
*/
if (irp->PendingReturned){
IoMarkIrpPending(irp);
}
return status;
}
#endif
VOID FunctionIdleNotificationCancelRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PDEVEXT devExt;
PFUNCTION_PDO_EXT functionPdoExt;
PPARENT_FDO_EXT parentFdoExt;
KIRQL oldIrql;
PIRP parentIdleIrpToCancel = NULL;
DBGVERBOSE(("Idle notification IRP %x cancelled", Irp));
IoReleaseCancelSpinLock(Irp->CancelIrql);
devExt = DeviceObject->DeviceExtension;
functionPdoExt = &devExt->functionPdoExt;
parentFdoExt = functionPdoExt->parentFdoExt;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
ASSERT(Irp == functionPdoExt->idleNotificationIrp);
functionPdoExt->idleNotificationIrp = NULL;
/*
* One of the functions on this composite device no longer wants
* the device to be idled. So we can no longer allow the parent
* device to be idle. Cancel it after we drop the spinlock.
*/
if (parentFdoExt->pendingIdleIrp){
parentIdleIrpToCancel = parentFdoExt->pendingIdleIrp;
parentFdoExt->pendingIdleIrp = NULL;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (parentIdleIrpToCancel){
IoCancelIrp(parentIdleIrpToCancel);
}
// Also, power up the parent here before we complete this Idle IRP.
//
// (HID will start to send requests immediately upon its completion,
// which may be before the parent's Idle IRP cancel routine is called
// which powers up the parent.)
if (parentFdoExt->state == STATE_SUSPENDED ||
parentFdoExt->pendingIdleIrp) {
ParentSetD0(parentFdoExt);
}
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
VOID CompleteFunctionIdleNotification(PFUNCTION_PDO_EXT functionPdoExt)
{
PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
NTSTATUS status;
KIRQL oldIrql;
PIRP irp;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
if (functionPdoExt->idleNotificationIrp){
PDRIVER_CANCEL oldCancelRoutine;
irp = functionPdoExt->idleNotificationIrp;
oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
if (oldCancelRoutine){
ASSERT(oldCancelRoutine == FunctionIdleNotificationCancelRoutine);
functionPdoExt->idleNotificationIrp = NULL;
}
else {
/*
* The irp was cancelled AND the cancel routine was called.
* The cancel routine will complete this irp, so don't complete
* it here.
*/
ASSERT(irp->Cancel);
irp = NULL;
}
}
else {
irp = NULL;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (irp) {
DBGVERBOSE(("Completing idle request IRP %x", irp));
irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
}
/*
* FunctionIdleNotificationRequest
*
*
* This function handles a request by a USB client driver to tell us
* that the device wants to idle (selective suspend).
*
*
*/
NTSTATUS FunctionIdleNotificationRequest(PFUNCTION_PDO_EXT functionPdoExt, PIRP Irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;
NTSTATUS ntStatus = STATUS_PENDING;
DBGVERBOSE(("Idle request %x, IRP %x", functionPdoExt, Irp));
idleCallbackInfo = (PUSB_IDLE_CALLBACK_INFO)
irpSp->Parameters.DeviceIoControl.Type3InputBuffer;
if (idleCallbackInfo && idleCallbackInfo->IdleCallback){
PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
BOOLEAN doCheckParentIdle = FALSE;
KIRQL oldIrql;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
if (functionPdoExt->idleNotificationIrp){
DBGVERBOSE(("Idle request: already have idle IRP"));
ntStatus = STATUS_DEVICE_BUSY;
}
else {
PDRIVER_CANCEL oldCancelRoutine;
functionPdoExt->idleNotificationIrp = Irp;
/*
* Must set cancel routine before checking Cancel flag
*/
oldCancelRoutine = IoSetCancelRoutine(Irp, FunctionIdleNotificationCancelRoutine);
ASSERT(!oldCancelRoutine);
if (Irp->Cancel){
/*
* Irp was cancelled. Check whether cancel routine was called.
*/
oldCancelRoutine = IoSetCancelRoutine(Irp, NULL);
if (oldCancelRoutine){
/*
* Cancel routine was NOT called. So complete the irp here.
*/
functionPdoExt->idleNotificationIrp = NULL;
ntStatus = STATUS_CANCELLED;
}
else {
/*
* Cancel routine was called, and it will complete the IRP
* as soon as we drop the spinlock.
* Return STATUS_PENDING so we don't touch the IRP.
*/
IoMarkIrpPending(Irp);
ntStatus = STATUS_PENDING;
}
}
else {
doCheckParentIdle = TRUE;
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (doCheckParentIdle){
/*
* See if we are ready to idle out this hub (after dropping spinlock).
*/
CheckParentIdle(parentFdoExt);
}
}
else {
DBGVERBOSE(("Idle request: No callback provided with idle IRP!"));
ntStatus = STATUS_NO_CALLBACK_ACTIVE;
}
return ntStatus;
}
/*
* FunctionInternalDeviceControl
*
*
* Note: this function cannot be pageable because internal
* ioctls may be sent at IRQL==DISPATCH_LEVEL.
*/
NTSTATUS FunctionInternalDeviceControl(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
ULONG ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
PURB urb;
USHORT urbFunc;
NTSTATUS status = NO_STATUS;
switch (ioControlCode){
case IOCTL_INTERNAL_USB_SUBMIT_URB:
urb = irpSp->Parameters.Others.Argument1;
ASSERT(urb);
DBG_LOG_URB(urb);
urbFunc = urb->UrbHeader.Function;
if (functionPdoExt->state != STATE_STARTED){
DBGWARN(("FunctionInternalDeviceControl: failing urb (func %xh) because child pdo state is %xh.", urbFunc, functionPdoExt->state));
status = STATUS_DEVICE_NOT_READY;
}
else if (functionPdoExt->parentFdoExt->state != STATE_STARTED){
DBGERR(("FunctionInternalDeviceControl: BAD PNP state! - child is started while parent has state %xh.", functionPdoExt->parentFdoExt->state));
status = STATUS_DEVICE_NOT_READY;
}
else {
switch (urbFunc){
case URB_FUNCTION_SELECT_CONFIGURATION:
status = UrbFunctionSelectConfiguration(functionPdoExt, urb);
irp->IoStatus.Information = 0;
break;
case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE:
status = UrbFunctionGetDescriptorFromDevice(functionPdoExt, urb);
break;
case URB_FUNCTION_SELECT_INTERFACE:
/*
* Pass this URB down to the parent
*/
ASSERT(urb->UrbSelectInterface.ConfigurationHandle);
break;
case URB_FUNCTION_ISOCH_TRANSFER:
/*
* Pass this URB down to the parent
*/
DBGSHOWISOCHPROGRESS();
break;
case URB_FUNCTION_ABORT_PIPE:
case URB_FUNCTION_RESET_PIPE:
/*
* Pass ABORT and RESET URBs down to the parent.
*/
DBGVERBOSE((DbgGetUrbName(urbFunc)));
break;
case URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE:
case URB_FUNCTION_CLASS_INTERFACE:
case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
default:
/*
* Pass unsupported URBs down to the parent
*/
DBGVERBOSE(("URB function %xh not implemented - passing to parent", (ULONG)urbFunc));
break;
}
}
/*
* Set the URB status
*/
switch (status){
case NO_STATUS: break;
case STATUS_PENDING: urb->UrbHeader.Status = USBD_STATUS_PENDING; break;
case STATUS_SUCCESS: urb->UrbHeader.Status = USBD_STATUS_SUCCESS; break;
default: urb->UrbHeader.Status = USBD_STATUS_ERROR; break;
}
break;
case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION:
status = FunctionIdleNotificationRequest(functionPdoExt, irp);
break;
case IOCTL_INTERNAL_USB_GET_BUS_INFO:
case IOCTL_INTERNAL_USB_GET_PORT_STATUS:
/*
* Leave the status as NO_STATUS so that these IRPs get passed down to the parent.
*/
break;
case IOCTL_INTERNAL_USB_RESET_PORT:
case IOCTL_INTERNAL_USB_CYCLE_PORT:
/*
* Pass RESET and CYCLE IRPs down to the parent.
* ParentInternalDeviceControl will synchronize
* multiple abort/resets on the parent device.
*/
DBGWARN(("RESET or CYCLE PORT -- pass down to parent"));
break;
case IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO:
TRAP("IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO - shouldn't get this");
status = STATUS_NOT_IMPLEMENTED;
break;
default:
/*
* This is not a pnp/power/syscntrl irp, so we fail unsupported irps
* with an actual error code (not with the default status).
*/
status = STATUS_NOT_SUPPORTED;
break;
}
if (status == NO_STATUS){
/*
* We didn't handle this IRP, so send it down to our own parent FDO.
*/
IoCopyCurrentIrpStackLocationToNext(irp);
#if DBG
IoSetCompletionRoutine(irp, FunctionIoctlCompletion, functionPdoExt, TRUE, TRUE, TRUE);
#endif
status = IoCallDriver(functionPdoExt->parentFdoExt->fdo, irp);
}
else if (status != STATUS_PENDING){
/*
* We serviced this IRP, so complete it.
*/
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
return status;
}
VOID FreeFunctionPDOResources(PFUNCTION_PDO_EXT functionPdoExt)
{
PAGED_CODE();
DBGVERBOSE(("Removing function PDO %xh (#%d)", functionPdoExt->pdo, functionPdoExt->functionIndex));
/*
* functionInterfaceList points inside
* the parent's interface list, so don't free it here.
*/
IoDeleteDevice(functionPdoExt->pdo);
}
PFUNCTION_PDO_EXT FindFunctionByIndex(PPARENT_FDO_EXT parentFdoExt, ULONG functionIndex)
{
PFUNCTION_PDO_EXT functionPdoExt = NULL;
KIRQL oldIrql;
ULONG i;
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;
if (thisFuncPdoExt->functionIndex == functionIndex){
functionPdoExt = thisFuncPdoExt;
break;
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
return functionPdoExt;
}
/*
********************************************************************************
* EnqueueFunctionWaitWakeIrp
********************************************************************************
*
* Enqueue the function's WaitWake IRP in the PARENT's queue.
* If no WaitWake IRP is pending on the parent, send one down.
*/
NTSTATUS EnqueueFunctionWaitWakeIrp(PFUNCTION_PDO_EXT functionPdoExt, PIRP irp)
{
PPARENT_FDO_EXT parentFdoExt = functionPdoExt->parentFdoExt;
PDRIVER_CANCEL oldCancelRoutine;
BOOLEAN submitParentWWirp = FALSE;
KIRQL oldIrql;
NTSTATUS status;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
/*
* Must set a cancel routine before checking the Cancel flag
* (this makes the cancel code path for the IRP have to contend
* for our local spinlock).
*/
oldCancelRoutine = IoSetCancelRoutine(irp, FunctionWaitWakeIrpCancelRoutine);
ASSERT(!oldCancelRoutine);
if (irp->Cancel){
/*
* This IRP has already been cancelled.
*/
oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
if (oldCancelRoutine){
/*
* Cancel routine was NOT called, so complete the IRP here
* (caller will do this when we return error).
*/
ASSERT(oldCancelRoutine == FunctionWaitWakeIrpCancelRoutine);
status = STATUS_CANCELLED;
}
else {
/*
* Cancel routine was called, and it will dequeue and complete the IRP
* as soon as we drop the spinlock.
* Initialize the IRP's listEntry so the dequeue doesn't corrupt the list.
* Then return STATUS_PENDING so we don't touch the IRP
*/
InitializeListHead(&irp->Tail.Overlay.ListEntry);
IoMarkIrpPending(irp);
status = STATUS_PENDING;
}
}
else {
/*
* Enqueue this WW irp in the parent's queue.
*/
InsertTailList(&parentFdoExt->functionWaitWakeIrpQueue, &irp->Tail.Overlay.ListEntry);
/*
* IoMarkIrpPending sets a bit in the current stack location
* to indicate that the Irp may complete on a different thread.
*/
IoMarkIrpPending(irp);
/*
* If a WW irp is not pending on the parent,
* then submit one after we drop the spinlock.
*/
if (!parentFdoExt->isWaitWakePending){
submitParentWWirp = TRUE;
parentFdoExt->isWaitWakePending = TRUE;
}
status = STATUS_PENDING;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
if (submitParentWWirp){
SubmitParentWaitWakeIrp(parentFdoExt);
}
return status;
}
/*
********************************************************************************
* FunctionWaitWakeIrpCancelRoutine
********************************************************************************
*
*/
VOID FunctionWaitWakeIrpCancelRoutine(IN PDEVICE_OBJECT deviceObject, IN PIRP irp)
{
PDEVEXT devExt = (PDEVEXT)deviceObject->DeviceExtension;
PFUNCTION_PDO_EXT functionPdoExt;
PPARENT_FDO_EXT parentFdoExt;
PIRP parentWaitWakeIrpToCancel = NULL;
KIRQL oldIrql;
ASSERT(devExt->signature == USBCCGP_TAG);
ASSERT(!devExt->isParentFdo);
functionPdoExt = &devExt->functionPdoExt;
parentFdoExt = functionPdoExt->parentFdoExt;
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
/*
* Dequeue the client's WaitWake IRP.
*/
RemoveEntryList(&irp->Tail.Overlay.ListEntry);
/*
* If the last function WaitWake IRP just got cancelled,
* cancel the parent's WaitWake IRP as well.
*/
if (IsListEmpty(&parentFdoExt->functionWaitWakeIrpQueue) &&
parentFdoExt->isWaitWakePending){
ASSERT(parentFdoExt->parentWaitWakeIrp);
parentWaitWakeIrpToCancel = parentFdoExt->parentWaitWakeIrp;
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
IoReleaseCancelSpinLock(irp->CancelIrql);
irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(irp, IO_NO_INCREMENT);
if (parentWaitWakeIrpToCancel){
// BUGBUG - slight race - what if parent WW irp completes just before this ?
// (we can't block in the completion routine, so there may not be a fix for this)
IoCancelIrp(parentWaitWakeIrpToCancel);
}
}
/*
* CompleteAllFunctionWaitWakeIrps
*
* Complete all WaitWake irps in the parent's queue
* with the given status.
*/
VOID CompleteAllFunctionWaitWakeIrps(PPARENT_FDO_EXT parentFdoExt, NTSTATUS status)
{
LIST_ENTRY irpsToComplete;
KIRQL oldIrql;
PLIST_ENTRY listEntry;
PIRP irp;
/*
* Complete all the irps in the parent's WW irp list.
* The irps can get resubmitted on the same thread that we
* complete them on; so in order to avoid an infinite loop,
* empty the list into a private queue first, then complete
* the irps out of the private queue.
*/
InitializeListHead(&irpsToComplete);
KeAcquireSpinLock(&parentFdoExt->parentFdoExtSpinLock, &oldIrql);
while (!IsListEmpty(&parentFdoExt->functionWaitWakeIrpQueue)){
PDRIVER_CANCEL oldCancelRoutine;
listEntry = RemoveHeadList(&parentFdoExt->functionWaitWakeIrpQueue);
InitializeListHead(listEntry); // in case cancel routine tries to dequeue again
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
if (oldCancelRoutine){
ASSERT(oldCancelRoutine == FunctionWaitWakeIrpCancelRoutine);
/*
* We can't complete an IRP while holding a spinlock.
* Also, we don't want to complete a WaitWake IRP while
* still processing queue because a driver
* may resend an IRP on the same thread, causing us to loop forever.
* So just move the IRPs to a private queue and we'll complete them later.
*/
InsertTailList(&irpsToComplete, listEntry);
}
else {
/*
* This IRP was cancelled and the cancel routine WAS called.
* The cancel routine will complete the IRP as soon as we drop the spinlock.
* So don't touch the IRP.
*/
ASSERT(irp->Cancel);
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
while (!IsListEmpty(&irpsToComplete)){
listEntry = RemoveHeadList(&irpsToComplete);
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
}
/*
* CompleteAllFunctionIdleIrps
*
* Complete all child function PDO Idle IRPs for the given parent
* with the given status.
*/
VOID CompleteAllFunctionIdleIrps(PPARENT_FDO_EXT parentFdoExt, NTSTATUS status)
{
LIST_ENTRY irpsToComplete;
KIRQL oldIrql;
PIRP irp;
ULONG i;
DBGVERBOSE(("Complete all child Idle IRPs for parent %x", parentFdoExt));
InitializeListHead(&irpsToComplete);
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;
irp = thisFuncPdoExt->idleNotificationIrp;
// complete the Idle IRP if we have one.
if (irp){
PDRIVER_CANCEL oldCancelRoutine;
oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
if (oldCancelRoutine){
ASSERT(oldCancelRoutine == FunctionIdleNotificationCancelRoutine);
InsertTailList(&irpsToComplete, &irp->Tail.Overlay.ListEntry);
thisFuncPdoExt->idleNotificationIrp = NULL;
}
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.
*/
ASSERT(irp->Cancel);
}
}
}
KeReleaseSpinLock(&parentFdoExt->parentFdoExtSpinLock, oldIrql);
while (!IsListEmpty(&irpsToComplete)){
PLIST_ENTRY listEntry;
listEntry = RemoveHeadList(&irpsToComplete);
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
}
/*
********************************************************************************
* InstallExtPropDesc
********************************************************************************
*
*
*/
VOID
InstallExtPropDesc (
IN PFUNCTION_PDO_EXT FunctionPdoExt
)
/*++
Routine Description:
This routines queries a device for an Extended Properties Descriptor, but
only once the very first time for a given instance of a device.
If the Extended Properties Descriptor and all of the Custom Property
Sections appear valid then each Custom Property section <ValueName,
ValueData> pair is installed in the device instance specific registry key
for the PDO.
The registry value entries would be found under this registry key:
HKLM\System\CCS\Enum\<DeviceID>\<InstanceID>\Device Parameters
Arguments:
FunctionPdoExt - The Function PDO Device Extension
Return Value:
None
--*/
{
PDEVICE_OBJECT deviceObject;
static WCHAR DidExtPropDescKey[] = L"ExtPropDescSemaphore";
ULONG didExtPropDesc;
MS_EXT_PROP_DESC_HEADER msExtPropDescHeader;
PMS_EXT_PROP_DESC pMsExtPropDesc;
ULONG bytesReturned;
NTSTATUS ntStatus;
PAGED_CODE();
deviceObject = FunctionPdoExt->pdo;
// Check if the semaphore value is already set in the registry. We only
// care whether or not it already exists, not what data it has.
//
ntStatus = GetPdoRegistryParameter(deviceObject,
DidExtPropDescKey,
NULL,
0,
NULL,
NULL);
if (NT_SUCCESS(ntStatus))
{
// Already did this once for this device instance. Don't do it again.
//
return;
}
// Set the semaphore key in the registry so that we only run the following
// code once per device.
didExtPropDesc = 1;
SetPdoRegistryParameter(deviceObject,
DidExtPropDescKey,
&didExtPropDesc,
sizeof(didExtPropDesc),
REG_DWORD,
PLUGPLAY_REGKEY_DEVICE);
RtlZeroMemory(&msExtPropDescHeader, sizeof(MS_EXT_PROP_DESC_HEADER));
// Request just the header of the MS Extended Property Descriptor
//
ntStatus = GetMsOsFeatureDescriptor(
FunctionPdoExt->parentFdoExt,
1, // Recipient Interface
(UCHAR)FunctionPdoExt->baseInterfaceNumber,
MS_EXT_PROP_DESCRIPTOR_INDEX,
&msExtPropDescHeader,
sizeof(MS_EXT_PROP_DESC_HEADER),
&bytesReturned);
// Make sure the MS Extended Property Descriptor header looks ok
//
if (NT_SUCCESS(ntStatus) &&
bytesReturned == sizeof(MS_EXT_PROP_DESC_HEADER) &&
msExtPropDescHeader.dwLength >= sizeof(MS_EXT_PROP_DESC_HEADER) &&
msExtPropDescHeader.bcdVersion == MS_EXT_PROP_DESC_VER &&
msExtPropDescHeader.wIndex == MS_EXT_PROP_DESCRIPTOR_INDEX &&
msExtPropDescHeader.wCount > 0)
{
// Allocate a buffer large enough for the entire descriptor
//
pMsExtPropDesc = ALLOCPOOL(NonPagedPool,
msExtPropDescHeader.dwLength);
if (pMsExtPropDesc)
{
RtlZeroMemory(pMsExtPropDesc, msExtPropDescHeader.dwLength);
// Request the entire MS Extended Property Descriptor
//
ntStatus = GetMsOsFeatureDescriptor(
FunctionPdoExt->parentFdoExt,
1, // Recipient Interface
(UCHAR)FunctionPdoExt->baseInterfaceNumber,
MS_EXT_PROP_DESCRIPTOR_INDEX,
pMsExtPropDesc,
msExtPropDescHeader.dwLength,
&bytesReturned);
if (NT_SUCCESS(ntStatus) &&
bytesReturned == msExtPropDescHeader.dwLength &&
RtlCompareMemory(&msExtPropDescHeader,
pMsExtPropDesc,
sizeof(MS_EXT_PROP_DESC_HEADER)) ==
sizeof(MS_EXT_PROP_DESC_HEADER))
{
// MS Extended Property Descriptor retrieved ok, parse and
// install each Custom Property Section it contains.
//
InstallExtPropDescSections(deviceObject,
pMsExtPropDesc);
}
// Done with the MS Extended Property Descriptor buffer, free it
//
FREEPOOL(pMsExtPropDesc);
}
}
}
/*
********************************************************************************
* InstallExtPropDescSections
********************************************************************************
*
*
*/
VOID
InstallExtPropDescSections (
PDEVICE_OBJECT DeviceObject,
PMS_EXT_PROP_DESC pMsExtPropDesc
)
/*++
Routine Description:
This routines parses an Extended Properties Descriptor and validates each
Custom Property Section contained in the Extended Properties Descriptor.
If all of the Custom Property Sections appear valid then each Custom
Property section <ValueName, ValueData> pair is installed in the device
instance specific registry key for the PDO.
The registry value entries would be found under this registry key:
HKLM\System\CCS\Enum\<DeviceID>\<InstanceID>\Device Parameters
Arguments:
DeviceObject - The PDO
pMsExtPropDesc - Pointer to an Extended Properties Descriptor buffer.
It is assumed that the header of this descriptor has
already been validated.
Return Value:
None
--*/
{
PUCHAR p;
PUCHAR end;
ULONG pass;
ULONG i;
ULONG dwSize;
ULONG dwPropertyDataType;
USHORT wPropertyNameLength;
PWCHAR bPropertyName;
ULONG dwPropertyDataLength;
PVOID bPropertyData;
NTSTATUS ntStatus;
PAGED_CODE();
// Get a pointer to the end of the entire Extended Properties Descriptor
//
end = (PUCHAR)pMsExtPropDesc + pMsExtPropDesc->Header.dwLength;
// First pass: Validate each Custom Property Section
// Second pass: Install each Custom Property Section (if first pass ok)
//
for (pass = 0; pass < 2; pass++)
{
// Get a pointer to the first Custom Property Section
//
p = (PUCHAR)&pMsExtPropDesc->CustomSection[0];
// Iterate over all of the Custom Property Sections
//
for (i = 0; i < pMsExtPropDesc->Header.wCount; i++)
{
ULONG offset;
// Make sure the dwSize field is in bounds
//
if (p + sizeof(ULONG) > end)
{
break;
}
// Extract the dwSize field and advance running offset
//
dwSize = *((PULONG)p);
offset = sizeof(ULONG);
// Make sure the entire structure is in bounds
//
if (p + dwSize > end)
{
break;
}
// Make sure the dwPropertyDataType field is in bounds
if (dwSize < offset + sizeof(ULONG))
{
break;
}
// Extract the dwPropertyDataType field and advance running offset
//
dwPropertyDataType = *((PULONG)(p + offset));
offset += sizeof(ULONG);
// Make sure the wPropertyNameLength field is in bounds
//
if (dwSize < offset + sizeof(USHORT))
{
break;
}
// Extract the wPropertyNameLength field and advance running offset
//
wPropertyNameLength = *((PUSHORT)(p + offset));
offset += sizeof(USHORT);
// Make sure the bPropertyName field is in bounds
//
if (dwSize < offset + wPropertyNameLength)
{
break;
}
// Set the bPropertyName pointer and advance running offset
//
bPropertyName = (PWCHAR)(p + offset);
offset += wPropertyNameLength;
// Make sure the dwPropertyDataLength field is in bounds
if (dwSize < offset + sizeof(ULONG))
{
break;
}
// Extract the dwPropertyDataLength field and advance running offset
//
dwPropertyDataLength = *((ULONG UNALIGNED*)(p + offset));
offset += sizeof(ULONG);
// Make sure the bPropertyData field is in bounds
//
if (dwSize < offset + dwPropertyDataLength)
{
break;
}
// Set the bPropertyData pointer and advance running offset
//
bPropertyData = p + offset;
offset += wPropertyNameLength;
// Make sure the dwPropertyDataType is valid
//
if (dwPropertyDataType < REG_SZ ||
dwPropertyDataType > REG_MULTI_SZ)
{
break;
}
// Make sure the wPropertyNameLength is valid
//
if (wPropertyNameLength == 0 ||
(wPropertyNameLength % sizeof(WCHAR)) != 0)
{
break;
}
// Make sure bPropertyName is NULL terminated
//
if (bPropertyName[(wPropertyNameLength / sizeof(WCHAR)) - 1] !=
UNICODE_NULL)
{
break;
}
// Everything looks ok,
//
if (pass > 0)
{
ntStatus = SetPdoRegistryParameter(
DeviceObject,
bPropertyName,
bPropertyData,
dwPropertyDataLength,
dwPropertyDataType,
PLUGPLAY_REGKEY_DEVICE);
}
}
// Skip the second pass if we bailed out of the first pass
//
if (i < pMsExtPropDesc->Header.wCount)
{
break;
}
}
}