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
65 KiB
1982 lines
65 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;
|
|
}
|
|
}
|
|
}
|