/*++ Copyright (c) Microsoft Corporation. All rights reserved. Module Name: pnpevent.c Abstract: Routines dealing with Plug and Play event management/notification. Author: Lonny McMichael (lonnym) 02/14/95 Paula Tomlinson (paulat) 07/01/96 Revision History: --*/ #include "pnpmgrp.h" #pragma hdrstop #include #include #include /* * Design notes: * * When UmPnpMgr needs to initiate an action (which it might do to complete * a CmXxx API), it calls NtPlugPlayControl. NtPlugPlayControl then usually * invokes one of the PpSetXxx functions. Similarly, if Io routines need to * initiate such an action (say due a hardware initiated eject), they call one * of the following PpSetXxx functions (or an intermediate): * * Operations synchronized via the queue * PpSetDeviceClassChange (async) * PpSetTargetDeviceRemove (optional event) * PpSetCustomTargetEvent (optional event) * PpSetHwProfileChangeEvent (optional event) * PpSetPowerEvent (optional event) * PpSetPlugPlayEvent (async) * PpSetDeviceRemovalSafe (optional event) * PpSetBlockedDriverEvent (async) * PpSynchronizeDeviceEventQueue (sync, enqueues a noop to flush queue) * * The PpSetXxx functions enqueue items to be processed into the Pnp event * queue (via PiInsertEventInQueue). Whenever one of these events are inserted * into the queue a worker routine is ensured to be available to process it * (PiWalkDeviceList). * * In general, events processed in PiWalkDeviceList fall into two categories - * those that are notifications for user mode (queued by kernel mode), and those * that are queued operations. * * User mode notifications are sent by invoking PiNotifyUserMode. That routine * gets UmPnpMgr's attention and copies up a buffer for it to digest. This * operation is synchronous, PiNotifyUserMode waits until UmPnpMgr.Dll signals * it is done (NtPlugPlayControl calls PiUserResponse) before returning. * * Queued operations (such as PiProcessQueryRemoveAndEject) may be very involved * and could generate other events solely for user mode (via calls to * PiNotifyUserMode, PiNotifyUserModeRemoveVetoed). These operations may also * need to synchronously call kernel and user mode code that registered for the * appropriate events (via the IopNotifyXxx functions). * */ // // Pool Tags // #define PNP_DEVICE_EVENT_LIST_TAG 'LEpP' #define PNP_DEVICE_EVENT_ENTRY_TAG 'EEpP' #define PNP_USER_BLOCK_TAG 'BUpP' #define PNP_DEVICE_WORK_ITEM_TAG 'IWpP' #define PNP_POOL_EVENT_BUFFER 'BEpP' // // PNP_USER_BLOCK // // The caller block contains info describing the caller of // NtGetPlugPlayEvent. There's only one caller block. // typedef struct _PNP_USER_BLOCK { NTSTATUS Status; ULONG Result; PPNP_VETO_TYPE VetoType; PUNICODE_STRING VetoName; ERESOURCE Lock; KEVENT Registered; KEVENT NotifyUserEvent; KEVENT UserResultEvent; PVOID PoolBuffer; ULONG PoolUsed; ULONG PoolSize; BOOLEAN Deferred; } PNP_USER_BLOCK, *PPNP_USER_BLOCK; // // Local (private) function prototypes // NTSTATUS PiInsertEventInQueue( IN PPNP_DEVICE_EVENT_ENTRY DeviceEvent ); VOID PiWalkDeviceList( IN PVOID Context ); NTSTATUS PiNotifyUserMode( PPNP_DEVICE_EVENT_ENTRY DeviceEvent ); NTSTATUS PiNotifyUserModeDeviceRemoval( IN PPNP_DEVICE_EVENT_ENTRY TemplateDeviceEvent, IN CONST GUID *EventGuid, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT PUNICODE_STRING VetoName OPTIONAL ); NTSTATUS PiNotifyUserModeRemoveVetoed( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PDEVICE_OBJECT DeviceObject, IN PNP_VETO_TYPE VetoType, IN PUNICODE_STRING VetoName OPTIONAL ); NTSTATUS PiNotifyUserModeRemoveVetoedByList( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PDEVICE_OBJECT DeviceObject, IN PNP_VETO_TYPE VetoType, IN PWSTR MultiSzVetoList ); NTSTATUS PiNotifyUserModeKernelInitiatedEject( IN PDEVICE_OBJECT DeviceObject, OUT PNP_VETO_TYPE *VetoType, OUT PUNICODE_STRING VetoName ); NTSTATUS PiProcessQueryRemoveAndEject( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ); NTSTATUS PiProcessTargetDeviceEvent( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ); NTSTATUS PiProcessCustomDeviceEvent( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ); NTSTATUS PiResizeTargetDeviceBlock( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent, IN PLUGPLAY_DEVICE_DELETE_TYPE DeleteType, IN PRELATION_LIST RelationsList, IN BOOLEAN ExcludeIndirectRelations ); VOID PiBuildUnsafeRemovalDeviceBlock( IN PPNP_DEVICE_EVENT_ENTRY OriginalDeviceEvent, IN PRELATION_LIST RelationsList, OUT PPNP_DEVICE_EVENT_ENTRY *AllocatedDeviceEvent ); VOID PiFinalizeVetoedRemove( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PNP_VETO_TYPE VetoType, IN PUNICODE_STRING VetoName OPTIONAL ); VOID LookupGuid( IN CONST GUID *Guid, IN OUT PCHAR String, IN ULONG StringLength ); VOID DumpMultiSz( IN PWCHAR MultiSz ); VOID DumpPnpEvent( IN PPLUGPLAY_EVENT_BLOCK EventBlock ); typedef struct { ULONG HandleCount; LOGICAL DumpHandles; LOGICAL CollectHandles; PUNICODE_STRING VetoString; } ENUM_HANDLES_CONTEXT, *PENUM_HANDLES_CONTEXT; LOGICAL PiCollectOpenHandles( IN PDEVICE_OBJECT *DeviceObjectArray, IN ULONG ArrayCount, IN LOGICAL KnownHandleFailure, IN OUT PUNICODE_STRING VetoString ); LOGICAL PiCollectOpenHandlesCallBack( IN PDEVICE_OBJECT DeviceObject, IN PEPROCESS Process, IN PFILE_OBJECT FileObject, IN HANDLE HandleId, IN PVOID Context ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, DumpMultiSz) #pragma alloc_text(PAGE, DumpPnpEvent) #pragma alloc_text(PAGE, LookupGuid) #pragma alloc_text(PAGE, PiCollectOpenHandles) #pragma alloc_text(PAGE, PiCollectOpenHandlesCallBack) #pragma alloc_text(PAGE, NtGetPlugPlayEvent) #pragma alloc_text(PAGE, PiCompareGuid) #pragma alloc_text(PAGE, PiInsertEventInQueue) #pragma alloc_text(PAGE, PiNotifyUserMode) #pragma alloc_text(PAGE, PiNotifyUserModeDeviceRemoval) #pragma alloc_text(PAGE, PiNotifyUserModeKernelInitiatedEject) #pragma alloc_text(PAGE, PiNotifyUserModeRemoveVetoed) #pragma alloc_text(PAGE, PiNotifyUserModeRemoveVetoedByList) #pragma alloc_text(PAGE, PiProcessCustomDeviceEvent) #pragma alloc_text(PAGE, PiProcessQueryRemoveAndEject) #pragma alloc_text(PAGE, PiProcessTargetDeviceEvent) #pragma alloc_text(PAGE, PiResizeTargetDeviceBlock) #pragma alloc_text(PAGE, PiBuildUnsafeRemovalDeviceBlock) #pragma alloc_text(PAGE, PiUserResponse) #pragma alloc_text(PAGE, PiWalkDeviceList) #pragma alloc_text(PAGE, PiFinalizeVetoedRemove) #pragma alloc_text(PAGE, PpCompleteDeviceEvent) #pragma alloc_text(PAGE, PpInitializeNotification) #pragma alloc_text(PAGE, PpNotifyUserModeRemovalSafe) #pragma alloc_text(PAGE, PpSetCustomTargetEvent) #pragma alloc_text(PAGE, PpSetDeviceClassChange) #pragma alloc_text(PAGE, PpSetDeviceRemovalSafe) #pragma alloc_text(PAGE, PpSetHwProfileChangeEvent) #pragma alloc_text(PAGE, PpSetBlockedDriverEvent) #pragma alloc_text(PAGE, PpSetPlugPlayEvent) #pragma alloc_text(PAGE, PpSetPowerEvent) #pragma alloc_text(PAGE, PpSetPowerVetoEvent) #pragma alloc_text(PAGE, PpSetTargetDeviceRemove) #pragma alloc_text(PAGE, PpSynchronizeDeviceEventQueue) #pragma alloc_text(PAGE, PiAllocateCriticalMemory) #pragma alloc_text(PAGE, PpSetInvalidIDEvent) #endif // // Global Data // #ifdef ALLOC_DATA_PRAGMA #pragma data_seg("PAGEDATA") #endif PPNP_DEVICE_EVENT_LIST PpDeviceEventList = NULL; PPNP_USER_BLOCK PpUserBlock = NULL; BOOLEAN PiUserModeRunning = FALSE; BOOLEAN PiNotificationInProgress = FALSE; PETHREAD PpDeviceEventThread = NULL; #ifdef ALLOC_DATA_PRAGMA #pragma data_seg() #endif KGUARDED_MUTEX PiNotificationInProgressLock; LOGICAL PiDumpVetoedHandles = FALSE; LOGICAL PiCollectVetoedHandles = FALSE; NTSTATUS NtGetPlugPlayEvent( IN HANDLE EventHandle, IN PVOID Context OPTIONAL, OUT PPLUGPLAY_EVENT_BLOCK EventBlock, IN ULONG EventBufferSize ) /*++ Routine Description: FRONT-END This Plug and Play Manager API allows the user-mode PnP manager to receive notification of a (kernel-mode) PnP hardware event. THIS API IS ONLY CALLABLE BY THE USER-MODE PNP MANAGER. IF ANY OTHER COMPONENT CALLS THIS API, THE DELIVERED EVENT WILL BE LOST TO THE REST OF THE OPERATING SYSTEM. FURTHERMORE, THERE IS COMPLEX SYNCHRONIZATION BETWEEN THE USER-MODE AND KERNEL-MODE PNP MANAGERS, ANYONE ELSE CALLING THIS API WILL EVENTUALLY DEADLOCK THE SYSTEM. Arguments: EventHandle - Supplies an event handle that is signalled when an event is ready to be delivered to user-mode. EventBlock - Pointer to a PLUGPLAY_EVENT_BLOCK structure that will receive information on the hardware event that has occurred. EventBufferLength - Specifies the size, in bytes, of the EventBuffer field in the PLUGPLAY_EVENT_BLOCK pointed to by EventBlock. Return Value: NTSTATUS code indicating whether or not the function was successful --*/ { NTSTATUS status; #if DBG CHAR guidString[256]; #endif UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(EventHandle); PAGED_CODE(); if (KeGetPreviousMode() != UserMode) { // // This routine only supports user-mode callers. // IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "NtGetPlugPlayEvent: Only allows user-mode callers\n")); return STATUS_ACCESS_DENIED; } if (!SeSinglePrivilegeCheck(SeTcbPrivilege, UserMode)) { // // Caller does not have "trusted computer base" privilge. // IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "NtGetPlugPlayEvent: Caller does not have \"trusted computer base\" privilge\n")); return STATUS_PRIVILEGE_NOT_HELD; } // // UMPNPMGR is now around. // PiUserModeRunning = TRUE; // // If we have not deferred on last call, we need to wait for kernel // to make data available. // status = STATUS_SUCCESS; if (!PpUserBlock->Deferred) { PpUserBlock->PoolUsed = 0; // // Tell kernel we have a waiter. // KeSetEvent(&PpUserBlock->Registered, 0, FALSE); // // Make it a UserMode wait so the terminate APC will unblock us, // and we can leave, which cleans up the thread // status = KeWaitForSingleObject(&PpUserBlock->NotifyUserEvent, Executive, UserMode, FALSE, NULL); } if (!NT_SUCCESS(status) || (status == STATUS_USER_APC) ) { return status; } // // Data is now available, validate user buffer size. // if (EventBufferSize < PpUserBlock->PoolUsed) { // // If user buffer is too small, then return appropriate status and // set the Deferred to TRUE so on the next call, we wont wait on kernel // mode (since data was already available). // PpUserBlock->Deferred = TRUE; IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "NtGetPlugPlayEvent: User-mode buffer too small for event\n")); return STATUS_BUFFER_TOO_SMALL; } // // User buffer is big enough so copy any data on success. // PpUserBlock->Deferred = FALSE; status = PpUserBlock->Status; if (NT_SUCCESS(status)) { if (PpUserBlock->PoolBuffer) { #if DBG LookupGuid(&((PPLUGPLAY_EVENT_BLOCK)(PpUserBlock->PoolBuffer))->EventGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "NtGetPlugPlayEvent: Returning event - EventGuid = %s\n", guidString)); #endif status = PiControlMakeUserModeCallersCopy( &EventBlock, PpUserBlock->PoolBuffer, PpUserBlock->PoolUsed, sizeof(ULONG), UserMode, FALSE); } else { IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "NtGetPlugPlayEvent: Invalid event buffer\n")); ASSERT(PpUserBlock->PoolBuffer); status = STATUS_UNSUCCESSFUL; } } return status; } // NtGetPlugPlayEvent NTSTATUS PpInitializeNotification( VOID ) /*++ Routine Description: This routine performs initialization required before any of the notification events can be processed. This routine performs init for the master device event queue processing. Parameters: None Return Value: Returns a STATUS_Xxx value that indicates whether the function succeeded or not. --*/ { NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); // // Allocate and initialize the master device event list. // PpDeviceEventList = ExAllocatePoolWithTag(NonPagedPool, sizeof(PNP_DEVICE_EVENT_LIST), PNP_DEVICE_EVENT_LIST_TAG); if (PpDeviceEventList == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Clean0; } KeInitializeMutex(&PpDeviceEventList->EventQueueMutex, 0); KeInitializeGuardedMutex(&PpDeviceEventList->Lock); InitializeListHead(&(PpDeviceEventList->List)); PpDeviceEventList->Status = STATUS_PENDING; // // Intialize the PpUserBlock buffer - this buffer contains info about // the user-mode caller for NtGetPlugPlayEvent and describes who in user // mode we pass the events to. // PpUserBlock = ExAllocatePoolWithTag(NonPagedPool, sizeof(PNP_USER_BLOCK), PNP_USER_BLOCK_TAG); if (PpUserBlock == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto Clean0; } RtlZeroMemory(PpUserBlock, sizeof(PNP_USER_BLOCK)); PpUserBlock->PoolSize = sizeof (PLUGPLAY_EVENT_BLOCK)+ sizeof (PNP_DEVICE_EVENT_ENTRY); PpUserBlock->PoolBuffer = ExAllocatePoolWithTag(NonPagedPool, PpUserBlock->PoolSize, PNP_USER_BLOCK_TAG); if (PpUserBlock->PoolBuffer == NULL ) { status = STATUS_INSUFFICIENT_RESOURCES; PpUserBlock->PoolSize = 0; goto Clean0; } KeInitializeEvent(&PpUserBlock->Registered, SynchronizationEvent, FALSE); KeInitializeEvent(&PpUserBlock->NotifyUserEvent, SynchronizationEvent, FALSE); KeInitializeEvent(&PpUserBlock->UserResultEvent, SynchronizationEvent, FALSE); ExInitializeResourceLite(&PpUserBlock->Lock); // PpUserBlock->Status = STATUS_SUCCESS; // PpUserBlock->Result = 0; // PpUserBlock->EventBuffer = NULL; // PpUserBlock->EventBufferSize = 0; // PpUserBlock->PoolUsed = 0; KeInitializeGuardedMutex(&PiNotificationInProgressLock); Clean0: return status; } // PpInitializeNotification NTSTATUS PiInsertEventInQueue( IN PPNP_DEVICE_EVENT_ENTRY DeviceEvent ) { PWORK_QUEUE_ITEM workItem; NTSTATUS status; PAGED_CODE(); workItem = NULL; status = STATUS_SUCCESS; #if DBG IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiInsertEventInQueue: Event queued\n")); DumpPnpEvent(&DeviceEvent->Data); #endif // // Check if a new work item needs to be kicked off. A new work item gets // kicked off iff this is the first event in the list. // KeAcquireGuardedMutex(&PpDeviceEventList->Lock); KeAcquireGuardedMutex(&PiNotificationInProgressLock); if (!PiNotificationInProgress) { workItem = ExAllocatePoolWithTag(NonPagedPool, sizeof(WORK_QUEUE_ITEM), PNP_DEVICE_WORK_ITEM_TAG); if (workItem) { PiNotificationInProgress = TRUE; KeClearEvent(&PiEventQueueEmpty); } else { IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "PiInsertEventInQueue: Could not allocate memory to kick off a worker thread\n")); status = STATUS_INSUFFICIENT_RESOURCES; } } else { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiInsertEventInQueue: Worker thread already running\n")); } // // Insert the event iff successfull so far. // InsertTailList(&PpDeviceEventList->List, &DeviceEvent->ListEntry); KeReleaseGuardedMutex(&PiNotificationInProgressLock); KeReleaseGuardedMutex(&PpDeviceEventList->Lock); // // Queue the work item if any. // if (workItem) { ExInitializeWorkItem(workItem, PiWalkDeviceList, workItem); ExQueueWorkItem(workItem, DelayedWorkQueue); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiInsertEventInQueue: Kicked off worker thread\n")); } return status; } NTSTATUS PpSetDeviceClassChange( IN CONST GUID *EventGuid, IN CONST GUID *ClassGuid, IN PUNICODE_STRING SymbolicLinkName ) /*++ Routine Description: This routine is called by user-mode pnp manager and drivers (indirectly) to submit device interface change events into a serialized asynchronous queue. This queue is processed by a work item. Arguments: EventGuid - Indicates what event is triggered has occured. ClassGuid - Indicates the class of the device interface that changed. SymbolicLinkName - Specifies the symbolic link name associated with the interface device. Return Value: None. --*/ { NTSTATUS status = STATUS_SUCCESS; ULONG dataSize, totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } #if DBG { CHAR eventGuidString[80]; CHAR classGuidString[80]; LookupGuid(EventGuid, eventGuidString, sizeof(eventGuidString)); LookupGuid(ClassGuid, classGuidString, sizeof(classGuidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetDeviceClassChange: Entered\n EventGuid = %s\n ClassGuid = %s\n SymbolicLinkName = %wZ\n", eventGuidString, classGuidString, SymbolicLinkName)); } #endif try { ASSERT(EventGuid != NULL); ASSERT(ClassGuid != NULL); ASSERT(SymbolicLinkName != NULL); // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // SymbolicLinkName field so it works out. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + SymbolicLinkName->Length; totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag( PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { status = STATUS_NO_MEMORY; goto Clean0; } RtlZeroMemory((PVOID)deviceEvent, totalSize); RtlCopyMemory(&deviceEvent->Data.EventGuid, EventGuid, sizeof(GUID)); deviceEvent->Data.EventCategory = DeviceClassChangeEvent; //deviceEvent->Data.Result = NULL; //deviceEvent->Data.Flags = 0; deviceEvent->Data.TotalSize = dataSize; RtlCopyMemory(&deviceEvent->Data.u.DeviceClass.ClassGuid, ClassGuid, sizeof(GUID)); RtlCopyMemory(&deviceEvent->Data.u.DeviceClass.SymbolicLinkName, SymbolicLinkName->Buffer, SymbolicLinkName->Length); deviceEvent->Data.u.DeviceClass.SymbolicLinkName[SymbolicLinkName->Length/sizeof(WCHAR)] = 0x0; status = PiInsertEventInQueue(deviceEvent); Clean0: NOTHING; } except(PiControlExceptionFilter(GetExceptionInformation())) { IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "PpSetDeviceClassChange: Exception 0x%08X\n", GetExceptionCode())); } return status; } // PpSetDeviceClassChange NTSTATUS PpSetCustomTargetEvent( IN PDEVICE_OBJECT DeviceObject, IN PKEVENT SyncEvent OPTIONAL, OUT PULONG Result OPTIONAL, IN PDEVICE_CHANGE_COMPLETE_CALLBACK Callback OPTIONAL, IN PVOID Context OPTIONAL, IN PTARGET_DEVICE_CUSTOM_NOTIFICATION NotificationStructure ) /*++ Routine Description: This routine is called by user-mode pnp manager and drivers (indirectly) to submit target device change events into a serialized asynchronous queue. This queue is processed by a work item. Arguments: DeviceObject - Indicates the device object for the device that changed. SyncEvent - Optionally, specifies a kernel-mode event that will be set when the event is finished processing. Result - Supplies a pointer to a ULONG that will be filled in with the status after the event has actual completed (notification finished and the event processed). This value is not used when SyncEvent is NULL and is REQUIRED when SyncEvent is supplied. NotificationStructure - Specifies the custom Notification to be processed. Return Value: None. --*/ { NTSTATUS status = STATUS_SUCCESS; PDEVICE_NODE deviceNode; ULONG dataSize, totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); ASSERT(NotificationStructure != NULL); ASSERT(DeviceObject != NULL); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetCustomTargetEvent: DeviceObject = 0x%p, SyncEvent = 0x%p, Result = 0x%p\n", DeviceObject, SyncEvent, Result)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " Callback = 0x%p, Context = 0x%p, NotificationStructure = 0x%p\n", Callback, Context, NotificationStructure)); if (SyncEvent) { ASSERT(Result); *Result = STATUS_PENDING; } if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(deviceNode); // // This is a custom event block, so build up the PLUGPLAY_EVENT_BLOCK // but copy the Notification Structure and put that in the EventBlock // so we can dig it out in the handler later // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + deviceNode->InstancePath.Length + sizeof(UNICODE_NULL); // // We need to ensure that the Notification structure remains aligned // so round up dataSize to a multiple of sizeof(PVOID). // dataSize += sizeof(PVOID) - 1; dataSize &= ~(sizeof(PVOID) - 1); dataSize += NotificationStructure->Size; totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = SyncEvent; deviceEvent->Callback = Callback; deviceEvent->Context = Context; deviceEvent->Data.EventGuid = GUID_PNP_CUSTOM_NOTIFICATION; deviceEvent->Data.EventCategory = CustomDeviceEvent; deviceEvent->Data.Result = Result; deviceEvent->Data.Flags = 0; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; if (deviceNode->InstancePath.Length != 0) { RtlCopyMemory((PVOID)deviceEvent->Data.u.CustomNotification.DeviceIds, (PVOID)deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); // // No need to NUL terminate this string since we initially zeroed the // buffer after allocation. // } // // Point the custom notification block to the extra space at the // end of the allocation // deviceEvent->Data.u.CustomNotification.NotificationStructure = (PVOID)((PUCHAR)deviceEvent + totalSize - NotificationStructure->Size); RtlCopyMemory(deviceEvent->Data.u.CustomNotification.NotificationStructure, NotificationStructure, NotificationStructure->Size); status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetCustomTargetEvent NTSTATUS PpSetTargetDeviceRemove( IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN KernelInitiated, IN BOOLEAN NoRestart, IN BOOLEAN OnlyRestartRelations, IN BOOLEAN DoEject, IN ULONG Problem, IN PKEVENT SyncEvent OPTIONAL, OUT PULONG Result OPTIONAL, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT PUNICODE_STRING VetoName OPTIONAL ) /*++ Routine Description: This routine is called by user-mode pnp manager and drivers (indirectly) to submit target device change events into a serialized asynchronous queue. This queue is processed by a work item. Arguments: EventGuid - Indicates what event is triggered has occured. DeviceObject - Indicates the device object for the device that changed. SyncEvent - Optionally, specifies a kernel-mode event that will be set when the event is finished processing. Result - Supplies a pointer to a ULONG that will be filled in with the status after the event has actual completed (notification finished and the event processed). This value is not used when SyncEvent is NULL and is REQUIRED when SyncEvent is supplied. Flags - Current can be set to the following flags (bitfields) TDF_PERFORMACTION TDF_DEVICEEJECTABLE. NotificationStructure - If present, implies that EventGuid is NULL, and specifies a custom Notification to be processed. By definition it cannot be an event of type GUID_TARGET_DEVICE_QUERY_REMOVE, GUID_TARGET_DEVICE_REMOVE_CANCELLED, or GUID_TARGET_DEVICE_REMOVE_COMPLETE. Return Value: None. --*/ { NTSTATUS status = STATUS_SUCCESS; PDEVICE_NODE deviceNode; ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); ASSERT(DeviceObject != NULL); if (SyncEvent) { ASSERT(Result); *Result = STATUS_PENDING; } IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetTargetDeviceRemove: Entered\n")); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceObject = 0x%p, NoRestart = %d, Problem = %d\n", DeviceObject, NoRestart, Problem)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " SyncEvent = 0x%p, Result = 0x%p\n", SyncEvent, Result)); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(deviceNode); // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // DeviceId field so it works out. Add one more for double-null term. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); dataSize += deviceNode->InstancePath.Length + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = SyncEvent; deviceEvent->Argument = Problem; deviceEvent->VetoType = VetoType; deviceEvent->VetoName = VetoName; deviceEvent->Data.EventGuid = DoEject ? GUID_DEVICE_EJECT : GUID_DEVICE_QUERY_AND_REMOVE; deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; deviceEvent->Data.Result = Result; if (NoRestart) { deviceEvent->Data.Flags |= TDF_NO_RESTART; } if (KernelInitiated) { deviceEvent->Data.Flags |= TDF_KERNEL_INITIATED; } if (OnlyRestartRelations) { ASSERT(!NoRestart); deviceEvent->Data.Flags |= TDF_ONLY_RESTART_RELATIONS; } deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; if (deviceNode->InstancePath.Length != 0) { RtlCopyMemory((PVOID)deviceEvent->Data.u.TargetDevice.DeviceIds, (PVOID)deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); } i = deviceNode->InstancePath.Length/sizeof(WCHAR); deviceEvent->Data.u.TargetDevice.DeviceIds[i] = L'\0'; status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetTargetDeviceRemove NTSTATUS PpSetDeviceRemovalSafe( IN PDEVICE_OBJECT DeviceObject, IN PKEVENT SyncEvent OPTIONAL, OUT PULONG Result OPTIONAL ) /*++ Routine Description: This routine is called to notify user mode a device can be removed. The IO system may queue this event when a hardware initiated eject has completed. Arguments: DeviceObject - Indicates the device object for the device that changed. SyncEvent - Optionally, specifies a kernel-mode event that will be set when the event is finished processing. Result - Supplies a pointer to a ULONG that will be filled in with the status after the event has actual completed (notification finished and the event processed). This value is not used when SyncEvent is NULL and is REQUIRED when SyncEvent is supplied. Return Value: None. --*/ { NTSTATUS status; PDEVICE_NODE deviceNode; ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); ASSERT(DeviceObject != NULL); if (SyncEvent) { ASSERT(Result); *Result = STATUS_PENDING; } IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetDeviceRemovalSafe: Entered\n")); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceObject = 0x%p, SyncEvent = 0x%p, Result = 0x%p\n", DeviceObject, SyncEvent, Result)); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(deviceNode); // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // DeviceId field so it works out. Add one more for double-null term. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); dataSize += deviceNode->InstancePath.Length + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = SyncEvent; deviceEvent->Argument = 0; deviceEvent->VetoType = NULL; deviceEvent->VetoName = NULL; deviceEvent->Data.EventGuid = GUID_DEVICE_SAFE_REMOVAL; deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; deviceEvent->Data.Result = Result; deviceEvent->Data.Flags = 0; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; if (deviceNode->InstancePath.Length != 0) { RtlCopyMemory((PVOID)deviceEvent->Data.u.TargetDevice.DeviceIds, (PVOID)deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); } i = deviceNode->InstancePath.Length/sizeof(WCHAR); deviceEvent->Data.u.TargetDevice.DeviceIds[i] = L'\0'; status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetDeviceRemovalSafe NTSTATUS PpSetHwProfileChangeEvent( IN GUID CONST *EventTypeGuid, IN PKEVENT CompletionEvent OPTIONAL, OUT PNTSTATUS CompletionStatus OPTIONAL, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT PUNICODE_STRING VetoName OPTIONAL ) { ULONG dataSize,totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); #if DBG { CHAR eventGuidString[80]; LookupGuid(EventTypeGuid, eventGuidString, sizeof(eventGuidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetHwProfileChangeEvent: Entered\n EventGuid = %s\n\n", eventGuidString)); } #endif if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); totalSize = dataSize + FIELD_OFFSET (PNP_DEVICE_EVENT_ENTRY,Data); deviceEvent = ExAllocatePoolWithTag (PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (NULL == deviceEvent) { return STATUS_INSUFFICIENT_RESOURCES; } // // Setup the PLUGPLAY_EVENT_BLOCK // RtlZeroMemory ((PVOID)deviceEvent,totalSize); deviceEvent->CallerEvent = CompletionEvent; deviceEvent->VetoType = VetoType; deviceEvent->VetoName = VetoName; deviceEvent->Data.EventCategory = HardwareProfileChangeEvent; RtlCopyMemory(&deviceEvent->Data.EventGuid, EventTypeGuid, sizeof(GUID)); deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.Result = (PULONG)CompletionStatus; status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetHwProfileChangeEvent NTSTATUS PpSetBlockedDriverEvent( IN GUID CONST *BlockedDriverGuid ) /*++ Routine Description: This routine is called to notify user mode of blocked driver events. Arguments: BlockedDriverGuid - Specifies the GUID which identifies the blocked driver. Return Value: Returns the status of inserting the event into the synchronized pnp event queue. --*/ { ULONG dataSize, totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; NTSTATUS status; PAGED_CODE(); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Allocate a device event entry. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } // // Setup the PLUGPLAY_EVENT_BLOCK // RtlZeroMemory ((PVOID)deviceEvent, totalSize); deviceEvent->Data.EventGuid = GUID_DRIVER_BLOCKED; deviceEvent->Data.EventCategory = BlockedDriverEvent; deviceEvent->Data.TotalSize = dataSize; RtlCopyMemory(&deviceEvent->Data.u.BlockedDriverNotification.BlockedDriverGuid, BlockedDriverGuid, sizeof(GUID)); // // Insert the event into the queue. // status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetBlockedDriverEvent NTSTATUS PpSetInvalidIDEvent( IN PUNICODE_STRING ParentInstance ) /*++ Routine Description: This routine is called to notify user mode when an invalid ID is encountered. Arguments: ParentInstance - Specifies the instance path of the parent of the device with invalid ID. Return Value: Returns the status of inserting the event into the synchronized pnp event queue. --*/ { ULONG dataSize, totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; NTSTATUS status; PAGED_CODE(); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Allocate a device event entry (note that we have one WCHAR reserved // as part of the block structure itself). // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + ParentInstance->Length + sizeof(UNICODE_NULL); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } // // Setup the PLUGPLAY_EVENT_BLOCK // RtlZeroMemory ((PVOID)deviceEvent, totalSize); deviceEvent->Data.EventGuid = GUID_DEVICE_INVALID_ID; deviceEvent->Data.EventCategory = InvalidIDEvent; deviceEvent->Data.TotalSize = dataSize; RtlCopyMemory(&deviceEvent->Data.u.InvalidIDNotification.ParentId[0], ParentInstance->Buffer, ParentInstance->Length); deviceEvent->Data.u.InvalidIDNotification.ParentId[ParentInstance->Length / sizeof(WCHAR)] = UNICODE_NULL; deviceEvent->Data.u.InvalidIDNotification.ParentId[(ParentInstance->Length / sizeof(WCHAR)) + 1] = UNICODE_NULL; // // Insert the event into the queue. // status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetInvalidIDEvent NTSTATUS PpSetPowerEvent( IN ULONG EventCode, IN ULONG EventData, IN PKEVENT CompletionEvent OPTIONAL, OUT PNTSTATUS CompletionStatus OPTIONAL, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT PUNICODE_STRING VetoName OPTIONAL ) /*++ Routine Description: This routine is called to notify user mode of system-wide power events. Arguments: EventCode - Supplies the power event code that is to be communicated to user-mode components. (Specifically, this event code is actually one of the PBT_APM* user-mode power event ids, as defined in sdk\inc\winuser.h. It is typically used as the WPARAM data associated with WM_POWERBROADCAST user-mode window messages. It is supplied to kernel-mode PnP, directly from win32k, for the explicit purpose of user-mode power event notification.) EventData - Specifies additional event-specific data for the specified power event id. (Specifically, this event data is the LPARAM data for the corresponding PBT_APM* user-mode power event id, specified above.) CompletionEvent - Optionally, specifies a kernel-mode event that will be set when the event is finished processing. CompletionStatus - Supplies a pointer to a ULONG that will be filled in with the status after the event has actual completed (notification finished and the event processed). This value is not used when SyncEvent is NULL and is REQUIRED when SyncEvent is supplied. VetoType - Optionally, if the specified EventCode is a query-type operation, this argument supplies a pointer to an address that will receive the type of the vetoing user-mode component, in the event that the request is denied. VetoName - Optionally, if the specified EventCode is a query-type operation, this argument supplies a pointer to a UNICODE_STRING that will receive the name of the vetoing user-mode component, in the event that the request is denied. Return Value: Returns the status of inserting the event into the synchronized pnp event queue. For the final status of a synchronous power event, check the value at the location specified by CompletionStatus, once the supplied CompletionEvent has been set. --*/ { ULONG dataSize,totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; NTSTATUS status = STATUS_SUCCESS; PAGED_CODE(); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetPowerEvent: Entered\n\n") ); if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); totalSize = dataSize + FIELD_OFFSET (PNP_DEVICE_EVENT_ENTRY,Data); deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (NULL == deviceEvent) { return STATUS_INSUFFICIENT_RESOURCES; } // //Setup the PLUGPLAY_EVENT_BLOCK // RtlZeroMemory ((PVOID)deviceEvent,totalSize); deviceEvent->CallerEvent = CompletionEvent; deviceEvent->VetoType = VetoType; deviceEvent->VetoName = VetoName; deviceEvent->Data.EventCategory = PowerEvent; deviceEvent->Data.EventGuid = GUID_PNP_POWER_NOTIFICATION; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.Result = (PULONG)CompletionStatus; deviceEvent->Data.u.PowerNotification.NotificationCode = EventCode; deviceEvent->Data.u.PowerNotification.NotificationData = EventData; status = PiInsertEventInQueue(deviceEvent); return status; } // PpSetPowerEvent NTSTATUS PpSetPowerVetoEvent( IN POWER_ACTION VetoedPowerOperation, IN PKEVENT CompletionEvent OPTIONAL, OUT PNTSTATUS CompletionStatus OPTIONAL, IN PDEVICE_OBJECT DeviceObject, IN PNP_VETO_TYPE VetoType, IN PUNICODE_STRING VetoName OPTIONAL ) /*++ --*/ { ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PDEVICE_NODE deviceNode; PWCHAR vetoData; NTSTATUS status; if (PpPnpShuttingDown) { return STATUS_TOO_LATE; } // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); // // Given the pdo, retrieve the devnode (the device instance string is // attached to the devnode in the InstancePath field). // deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; if (!deviceNode) { ObDereferenceObject(DeviceObject); return STATUS_INVALID_PARAMETER_2; } // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (because of the first index into // the DeviceIdVetoNameBuffer, this is double null terminated). // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + deviceNode->InstancePath.Length + (VetoName ? VetoName->Length : 0) + sizeof(WCHAR)*2; totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = CompletionEvent; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; deviceEvent->Data.Result = (PULONG)CompletionStatus; deviceEvent->Data.u.VetoNotification.VetoType = VetoType; // // You can think of this as a MultiSz string where the first entry is the // DeviceId for the device being removed, and the next Id's all corrospond // to the vetoers. // RtlCopyMemory( deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer, deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length ); i = deviceNode->InstancePath.Length; deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)] = UNICODE_NULL; if (VetoName) { vetoData = (&deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)])+1; RtlCopyMemory(vetoData, VetoName->Buffer, VetoName->Length); vetoData[VetoName->Length/sizeof(WCHAR)] = UNICODE_NULL; } // // No need to NULL terminate the entry after the last one as we prezero'd // the buffer. Now set the appropriate GUID so the UI looks right. // if (VetoedPowerOperation == PowerActionWarmEject) { deviceEvent->Data.EventGuid = GUID_DEVICE_WARM_EJECT_VETOED; } else if (VetoedPowerOperation == PowerActionHibernate) { deviceEvent->Data.EventGuid = GUID_DEVICE_HIBERNATE_VETOED; } else { deviceEvent->Data.EventGuid = GUID_DEVICE_STANDBY_VETOED; } deviceEvent->Data.EventCategory = VetoEvent; status = PiInsertEventInQueue(deviceEvent); return status; } VOID PpSetPlugPlayEvent( IN CONST GUID *EventGuid, IN PDEVICE_OBJECT DeviceObject ) /*++ Routine Description: This routine allows kernel mode enumerator to inform Plug and Play Manager on the events triggered by enumeration, i.e., DeviceArrived and DeviceRemoved. The PnP manager can then inform user-mode about the event. Arguments: EventId - Indicates what event is triggered by enumeration. Return Value: None. --*/ { ULONG dataSize, totalSize; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PDEVICE_NODE deviceNode; PAGED_CODE(); ASSERT(EventGuid != NULL); ASSERT(DeviceObject != NULL); #if DBG { CHAR eventGuidString[80]; LookupGuid(EventGuid, eventGuidString, sizeof(eventGuidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpSetPlugPlayEvent: Entered\n EventGuid = %s\n", eventGuidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceObject = 0x%p\n", DeviceObject)); } #endif if (PpPnpShuttingDown) { return; } // // Given the pdo, retrieve the devnode (the device instance string is // attached to the devnode in the InstancePath field). // deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; if (deviceNode == NULL) { return; } // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // DeviceId field. also include a final terminating null, in case this is // a TargetDevice event, where DeviceIds is a multi-sz list). // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + deviceNode->InstancePath.Length + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { return; } RtlZeroMemory((PVOID)deviceEvent, totalSize); RtlCopyMemory(&deviceEvent->Data.EventGuid, EventGuid, sizeof(GUID)); deviceEvent->Data.TotalSize = dataSize; if (PiCompareGuid(EventGuid, &GUID_DEVICE_ENUMERATED)) { // // GUID_DEVICE_ENUMERATED events are device installation requests for // user-mode, and are sent using the DeviceInstallEvent event type. // deviceEvent->Data.EventCategory = DeviceInstallEvent; RtlCopyMemory(&deviceEvent->Data.u.InstallDevice.DeviceId, deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); deviceEvent->Data.u.InstallDevice.DeviceId[deviceNode->InstancePath.Length/sizeof(WCHAR)] = 0x0; } else { // // All other target events are sent using the TargetDeviceChangeEvent // event type, and are distinguished by the EventGuid. Note that // DeviceIds is a multi-sz list. // deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; RtlCopyMemory(&deviceEvent->Data.u.TargetDevice.DeviceIds, deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); deviceEvent->Data.u.TargetDevice.DeviceIds[deviceNode->InstancePath.Length/sizeof(WCHAR)] = 0x0; deviceEvent->Data.u.TargetDevice.DeviceIds[deviceNode->InstancePath.Length/sizeof(WCHAR)+1] = 0x0; } // // Don't hold a reference count against the DO for these events. This can // lead to really annoying Critical Device Database vs. Installation vs. // I/O driver refcount races. // if (PiCompareGuid(EventGuid, &GUID_DEVICE_ENUMERATED) || PiCompareGuid(EventGuid, &GUID_DEVICE_ARRIVAL)) { DeviceObject = NULL; } else { // // Reference the device object so it can't go away until after we're // done with notification. // ASSERT(DeviceObject); ObReferenceObject(DeviceObject); } deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; PiInsertEventInQueue(deviceEvent); return; } // PpSetPlugPlayEvent NTSTATUS PpSynchronizeDeviceEventQueue( VOID ) { NTSTATUS status; PPNP_DEVICE_EVENT_ENTRY deviceEvent; KEVENT event; ULONG result; PAGED_CODE(); // // Note this is the only queuing function which is valid when PpShuttingDown // is TRUE. // deviceEvent = ExAllocatePoolWithTag( PagedPool, sizeof(PNP_DEVICE_EVENT_ENTRY), PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { return STATUS_NO_MEMORY; } KeInitializeEvent(&event, NotificationEvent, FALSE); RtlZeroMemory((PVOID)deviceEvent, sizeof(PNP_DEVICE_EVENT_ENTRY)); deviceEvent->CallerEvent = &event; deviceEvent->Data.EventGuid = GUID_DEVICE_NOOP; deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; deviceEvent->Data.Result = &result; deviceEvent->Data.TotalSize = sizeof(PLUGPLAY_EVENT_BLOCK); status = PiInsertEventInQueue(deviceEvent); if (NT_SUCCESS(status)) { status = KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, // not alertable 0); // infinite } return status; } VOID PiWalkDeviceList( IN PVOID Context ) /*++ Routine Description: If the master device list contains any device events, empty the list now. This is a worker item thread routine (queued by PiPostNotify). We walk the list - this will cause the oldest device event on the list to be sent to all registered recipients and then the device event will be removed (if at least one recipient received it). Order Rules: Interface Devices - kernel mode first, user-mode second Hardware profile changes - user-mode first, kernel-mode second Target device changes (query remove, remove) : user-mode first, send (cancel remove) : kernel-mode first, post (custom) : kernel-mode first, post Arguments: NONE. Return Value: NONE. --*/ { NTSTATUS status; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PLIST_ENTRY current; UNICODE_STRING tempString; PDEVICE_NODE deviceNode; PAGED_CODE(); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiWalkDeviceList: Worker thread entered\n")); PpDeviceEventThread = PsGetCurrentThread(); // // Empty the device event list, remove entries from the head of the list // (deliver oldest entries first). // // As this function always executes in a system process workitem, we // don't have to grab a critical region lock. // status = KeWaitForSingleObject(&PpDeviceEventList->EventQueueMutex, Executive, KernelMode, FALSE, // not alertable 0); // infinite if (!NT_SUCCESS(status)) { KeAcquireGuardedMutex(&PiNotificationInProgressLock); KeSetEvent(&PiEventQueueEmpty, 0, FALSE); PiNotificationInProgress = FALSE; KeReleaseGuardedMutex(&PiNotificationInProgressLock); PpDeviceEventThread = NULL; return; } for ( ; ; ) { KeAcquireGuardedMutex(&PpDeviceEventList->Lock); if (!IsListEmpty(&PpDeviceEventList->List)) { current = RemoveHeadList(&PpDeviceEventList->List); KeReleaseGuardedMutex(&PpDeviceEventList->Lock); deviceEvent = CONTAINING_RECORD(current, // address PNP_DEVICE_EVENT_ENTRY, // type ListEntry); // field #if DBG { CHAR guidString[256]; LookupGuid(&deviceEvent->Data.EventGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiWalkDeviceList: Processing queued event - EventGuid = %s\n", guidString)); } #endif status = STATUS_SUCCESS; if (deviceEvent->Data.DeviceObject != NULL) { deviceNode = PP_DO_TO_DN((PDEVICE_OBJECT)deviceEvent->Data.DeviceObject); if (deviceNode == NULL || PipIsDevNodeDeleted(deviceNode)) { status = STATUS_NO_SUCH_DEVICE; } } if (NT_SUCCESS(status)) { switch (deviceEvent->Data.EventCategory) { case DeviceClassChangeEvent: { // // Notify kernel-mode (synchronous). // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiWalkDeviceList: DeviceClassChangeEvent - notifying kernel-mode\n")); RtlInitUnicodeString(&tempString, deviceEvent->Data.u.DeviceClass.SymbolicLinkName); IopNotifyDeviceClassChange(&deviceEvent->Data.EventGuid, &deviceEvent->Data.u.DeviceClass.ClassGuid, &tempString); // // Notify user-mode (synchronous). // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiWalkDeviceList: DeviceClassChangeEvent - user kernel-mode\n")); PiNotifyUserMode(deviceEvent); status = STATUS_SUCCESS; break; } case CustomDeviceEvent: { status = PiProcessCustomDeviceEvent(&deviceEvent); break; } case TargetDeviceChangeEvent: { status = PiProcessTargetDeviceEvent(&deviceEvent); break; } case DeviceInstallEvent: { // // Notify user-mode (synchronous). // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiWalkDeviceList: DeviceInstallEvent - notifying user-mode\n")); PiNotifyUserMode(deviceEvent); status = STATUS_SUCCESS; break; } case HardwareProfileChangeEvent: { // // Notify user-mode (synchronous). // status = PiNotifyUserMode(deviceEvent); if (NT_SUCCESS(status)) { // // Notify K-Mode // IopNotifyHwProfileChange(&deviceEvent->Data.EventGuid, deviceEvent->VetoType, deviceEvent->VetoName); } break; } case PowerEvent: { // // Notify user-mode (synchronous). // status = PiNotifyUserMode(deviceEvent); break; } case VetoEvent: { // // Forward onto user-mode. // status = PiNotifyUserMode(deviceEvent); break; } case BlockedDriverEvent: { // // Forward onto user-mode. // status = PiNotifyUserMode(deviceEvent); break; } case InvalidIDEvent: { // // Forward onto user-mode. // status = PiNotifyUserMode(deviceEvent); break; } default: { // // These should never be queued to kernel mode. They are // notifications for user mode, and should only be seen // through the PiNotifyUserModeXxx functions. // ASSERT(0); status = STATUS_UNSUCCESSFUL; break; } } } if (status != STATUS_PENDING) { PpCompleteDeviceEvent(deviceEvent, status); } // // Commit pending registrations after processing each event. // IopProcessDeferredRegistrations(); } else { KeAcquireGuardedMutex(&PiNotificationInProgressLock); KeSetEvent(&PiEventQueueEmpty, 0, FALSE); PiNotificationInProgress = FALSE; // // Commit pending registrations after processing all queued events. // IopProcessDeferredRegistrations(); KeReleaseGuardedMutex(&PiNotificationInProgressLock); KeReleaseGuardedMutex(&PpDeviceEventList->Lock); break; } } if (Context != NULL) { ExFreePool(Context); } PpDeviceEventThread = NULL; KeReleaseMutex(&PpDeviceEventList->EventQueueMutex, FALSE); return; } // PiWalkDeviceList VOID PpCompleteDeviceEvent( IN OUT PPNP_DEVICE_EVENT_ENTRY DeviceEvent, IN NTSTATUS FinalStatus ) /*++ Routine Description: Arguments: DeviceEvent - Event to complete. FinalStatus - Final status for this event. Return Value: NONE. --*/ { #if DBG CHAR guidString[256]; #endif PAGED_CODE(); #if DBG LookupGuid(&DeviceEvent->Data.EventGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PpCompleteDeviceEvent: Completing queued event - EventGuid = %s with %08lx\n", guidString, FinalStatus)); #endif // // If synchronous, signal the user-supplied event. // if (DeviceEvent->CallerEvent) { *DeviceEvent->Data.Result = FinalStatus; KeSetEvent(DeviceEvent->CallerEvent, 0, FALSE); } if (DeviceEvent->Callback) { DeviceEvent->Callback(DeviceEvent->Context); } // // Release the reference we took for this device object during // the PpSetCustomTargetEvent call. // if (DeviceEvent->Data.DeviceObject != NULL) { ObDereferenceObject(DeviceEvent->Data.DeviceObject); } // // Assume device event was delivered successfully, get rid of it. // ExFreePool(DeviceEvent); return; } // PpCompleteDeviceEvent NTSTATUS PiNotifyUserMode( PPNP_DEVICE_EVENT_ENTRY DeviceEvent ) /*++ Routine Description: This routine dispatches the device event to user-mode for processing. Arguments: DeviceEvent - Data describing what change and how. Return Value: Retuns an NTSTATUS value. --*/ { NTSTATUS status = STATUS_SUCCESS, status1 = STATUS_SUCCESS; PAGED_CODE(); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiNotifyUserMode: Entered\n")); // // First make sure user-mode is up and running before attempting to deliver // an event. If not running yet, skip user-mode for this event. // if (PiUserModeRunning) { // // User-mode notification is a single-shot model, once user-mode is // running, I need to wait until user-mode is ready to take the next // event (i.e, wait until we're sitting in another NtGetPlugPlayEvent // call). // status1 = KeWaitForSingleObject(&PpUserBlock->Registered, Executive, KernelMode, FALSE, NULL); ASSERT (PpUserBlock->Deferred == FALSE); // // Change the status after the wait. // PpUserBlock->Status = STATUS_SUCCESS; if (NT_SUCCESS(status1)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiNotifyUserMode: User-mode ready\n")); // // Make sure we can handle it in the pool buffer and copy it out // if (PpUserBlock->PoolSize < DeviceEvent->Data.TotalSize) { // //Allocate a new block (well, conceptually grow the block) // only when it's not big enough, so that we know we've always got // room for normal events, and in the very low memory case, we can // fail custom events, but keep the system running // PVOID pHold; pHold = ExAllocatePoolWithTag(NonPagedPool, DeviceEvent->Data.TotalSize, PNP_POOL_EVENT_BUFFER); if (!pHold) { IopDbgPrint((IOP_IOEVENT_ERROR_LEVEL, "PiNotifyUserMode: Out of NonPagedPool!!\n")); PpUserBlock->Status = STATUS_INSUFFICIENT_RESOURCES; return STATUS_INSUFFICIENT_RESOURCES; } PpUserBlock->PoolSize = DeviceEvent->Data.TotalSize; ExFreePool (PpUserBlock->PoolBuffer ); PpUserBlock->PoolBuffer = pHold; } PpUserBlock->PoolUsed = DeviceEvent->Data.TotalSize; RtlCopyMemory(PpUserBlock->PoolBuffer, &DeviceEvent->Data, PpUserBlock->PoolUsed); } // // Veto information is only propogated where needed, ie // QUERY_REMOVE's, Profile change requests and PowerEvents. // if (PiCompareGuid(&DeviceEvent->Data.EventGuid, &GUID_TARGET_DEVICE_QUERY_REMOVE) || PiCompareGuid(&DeviceEvent->Data.EventGuid, &GUID_HWPROFILE_QUERY_CHANGE) || PiCompareGuid(&DeviceEvent->Data.EventGuid, &GUID_DEVICE_KERNEL_INITIATED_EJECT) || (DeviceEvent->Data.EventCategory == PowerEvent)) { PpUserBlock->VetoType = DeviceEvent->VetoType; PpUserBlock->VetoName = DeviceEvent->VetoName; } else { PpUserBlock->VetoType = NULL; PpUserBlock->VetoName = NULL; } // // Set the system event that causes NtGetPlugPlayEvent to return to caller. // KeSetEvent(&PpUserBlock->NotifyUserEvent, 0, FALSE); // // Wait until we get an answer back from user-mode. // // ADRIAO N.B. 2002/03/24 - This checks for Kernel mode alerts??? // Bizarre! // status1 = KeWaitForSingleObject(&PpUserBlock->UserResultEvent, Executive, KernelMode, TRUE, NULL); // // Check the result from this user-mode notification. // if (status1 == STATUS_ALERTED || status1 == STATUS_SUCCESS) { if (!PpUserBlock->Result) { // // For query-remove case, any errors are treated as a // failure during notification (since it may result in our // inability to let a registered caller vote in the query-remove) // and the PpUserBlock->Result is set accordingly. // // // Note! User mode ONLY returns a 0 or !0 response. // if 1 then it succeeded. // status = STATUS_UNSUCCESSFUL; } } PpUserBlock->VetoType = NULL; PpUserBlock->VetoName = NULL; } IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiNotifyUserMode: User-mode returned, status = 0x%08X, status1 = 0x%08X, Result = 0x%08X\n", status, status1, PpUserBlock->Result)); return status; } // PiNotifyUserMode VOID PiUserResponse( IN ULONG Response, IN PNP_VETO_TYPE VetoType, IN LPWSTR VetoName, IN ULONG VetoNameLength ) /*++ Routine Description: This routine is called when user-mode pnp manager is signalling that it's done processing an event; the result of the event processing is passed in the Response parameter. Arguments: Response - Result of event processing in user-mode. Return Value: None. --*/ { UNICODE_STRING vetoString; PAGED_CODE(); PpUserBlock->Result = Response; if (PpUserBlock->VetoType != NULL) { *PpUserBlock->VetoType = VetoType; } if (PpUserBlock->VetoName != NULL) { ASSERT(VetoNameLength == (USHORT)VetoNameLength); vetoString.MaximumLength = (USHORT)VetoNameLength; vetoString.Length = (USHORT)VetoNameLength; vetoString.Buffer = VetoName; RtlCopyUnicodeString(PpUserBlock->VetoName, &vetoString); } KeSetEvent(&PpUserBlock->UserResultEvent, 0, FALSE); } // PiUserResponse NTSTATUS PiNotifyUserModeDeviceRemoval( IN PPNP_DEVICE_EVENT_ENTRY TemplateDeviceEvent, IN CONST GUID *EventGuid, OUT PPNP_VETO_TYPE VetoType OPTIONAL, OUT PUNICODE_STRING VetoName OPTIONAL ) /*++ Routine Description: This routine tells user mode to perform a specific device removal operation. Arguments: TemplateDeviceEvent - Device event containing information about the intended event (includes a list of devices.) The event is temporarily used by this function, and is restored before this function returns. EventGuid - Points to the event user mode should process: GUID_TARGET_DEVICE_QUERY_REMOVE GUID_TARGET_DEVICE_REMOVE_CANCELLED GUID_DEVICE_REMOVE_PENDING GUID_TARGET_DEVICE_REMOVE_COMPLETE GUID_DEVICE_SURPRISE_REMOVAL VetoType - Pointer to address that receives the veto type if the operation failed. VetoName - Pointer to a unicode string that will receive data appropriate to the veto type. Return Value: NTSTATUS. --*/ { NTSTATUS status; GUID oldGuid; PPNP_VETO_TYPE oldVetoType; PUNICODE_STRING oldVetoName; #if DBG CHAR guidString[256]; #endif PAGED_CODE(); #if DBG LookupGuid(EventGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiNotifyUserModeDeviceRemoval: %s - notifying user-mode\n", guidString)); #endif // // Save the old guid so we can use the template without making a copy. We // preserve it so that the removal veto UI can use the original event GUID // to let the UI differentiate disables from ejects, etc. // RtlCopyMemory(&oldGuid, &TemplateDeviceEvent->Data.EventGuid, sizeof(GUID)); // // Do the same with the vetoname and vetobuffer. // oldVetoType = TemplateDeviceEvent->VetoType; oldVetoName = TemplateDeviceEvent->VetoName; // // Copy in the new data. // RtlCopyMemory(&TemplateDeviceEvent->Data.EventGuid, EventGuid, sizeof(GUID)); TemplateDeviceEvent->VetoType = VetoType; TemplateDeviceEvent->VetoName = VetoName; // // Send it. // status = PiNotifyUserMode(TemplateDeviceEvent); // // Restore the old info. // RtlCopyMemory(&TemplateDeviceEvent->Data.EventGuid, &oldGuid, sizeof(GUID)); TemplateDeviceEvent->VetoType = oldVetoType; TemplateDeviceEvent->VetoName = oldVetoName; return status; } NTSTATUS PiNotifyUserModeKernelInitiatedEject( IN PDEVICE_OBJECT DeviceObject, OUT PNP_VETO_TYPE *VetoType, OUT PUNICODE_STRING VetoName ) /*++ Routine Description: This routine is called to notify user mode a device has a kenel-mode initated eject outstanding. UmPnpMgr might decide to veto the event if a user with the appropriate permissions hasn't logged on locally. Arguments: DeviceObject - Indicates the device object is to be ejected. Return Value: None. --*/ { NTSTATUS status; PDEVICE_NODE deviceNode; ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); ASSERT(DeviceObject != NULL); // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(deviceNode); // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // DeviceId field so it works out. Add one more for double-null term. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); dataSize += deviceNode->InstancePath.Length + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_NO_MEMORY; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = NULL; deviceEvent->Argument = 0; deviceEvent->VetoType = VetoType; deviceEvent->VetoName = VetoName; deviceEvent->Data.EventGuid = GUID_DEVICE_KERNEL_INITIATED_EJECT; deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; deviceEvent->Data.Result = 0; deviceEvent->Data.Flags = 0; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; if (deviceNode->InstancePath.Length != 0) { RtlCopyMemory((PVOID)deviceEvent->Data.u.TargetDevice.DeviceIds, (PVOID)deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); } i = deviceNode->InstancePath.Length/sizeof(WCHAR); deviceEvent->Data.u.TargetDevice.DeviceIds[i] = L'\0'; status = PiNotifyUserMode(deviceEvent); ExFreePool(deviceEvent); ObDereferenceObject(DeviceObject); return status; } // PiNotifyUserModeKernelInitiatedEject NTSTATUS PiNotifyUserModeRemoveVetoed( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PDEVICE_OBJECT DeviceObject, IN PNP_VETO_TYPE VetoType, IN PUNICODE_STRING VetoName OPTIONAL ) /*++ --*/ { ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PDEVICE_NODE deviceNode; PWCHAR vetoData; NTSTATUS status; // // This device should be locked during this operation, but all good designs // includes healthy doses of paranoia. // ObReferenceObject(DeviceObject); // // Given the pdo, retrieve the devnode (the device instance string is // attached to the devnode in the InstancePath field). // deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; if (!deviceNode) { ObDereferenceObject(DeviceObject); return STATUS_INVALID_PARAMETER_1; } // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (because of the first index into // the DeviceIdVetoNameBuffer, this is double null terminated). // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + deviceNode->InstancePath.Length + (VetoName ? VetoName->Length : 0) + sizeof(WCHAR)*2; totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; deviceEvent->Data.u.VetoNotification.VetoType = VetoType; // // You can think of this as a MultiSz string where the first entry is the // DeviceId for the device being removed, and the next Id's all corrospond // to the vetoers. // RtlCopyMemory( deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer, deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length ); i = deviceNode->InstancePath.Length; deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)] = UNICODE_NULL; if (VetoName) { vetoData = (&deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)])+1; RtlCopyMemory(vetoData, VetoName->Buffer, VetoName->Length); vetoData[VetoName->Length/sizeof(WCHAR)] = UNICODE_NULL; } // // No need to NULL terminate the entry after the last one as we prezero'd // the buffer. Now set the appropriate GUID so the UI looks right. // if (PiCompareGuid(&VetoedDeviceEvent->Data.EventGuid, &GUID_DEVICE_EJECT)) { deviceEvent->Data.EventGuid = GUID_DEVICE_EJECT_VETOED; } else { ASSERT(PiCompareGuid(&VetoedDeviceEvent->Data.EventGuid, &GUID_DEVICE_QUERY_AND_REMOVE)); deviceEvent->Data.EventGuid = GUID_DEVICE_REMOVAL_VETOED; } deviceEvent->Data.EventCategory = VetoEvent; status = PiNotifyUserMode(deviceEvent); ExFreePool(deviceEvent); ObDereferenceObject(DeviceObject); return status; } NTSTATUS PiNotifyUserModeRemoveVetoedByList( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PDEVICE_OBJECT DeviceObject, IN PNP_VETO_TYPE VetoType, IN PWSTR MultiSzVetoList ) /*++ --*/ { ULONG dataSize, totalSize, i, vetoListLength; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PDEVICE_NODE deviceNode; PWCHAR vetoData; NTSTATUS status; // // This device should be locked during this operation, but all good designs // includes healthy doses of paranoia. // ObReferenceObject(DeviceObject); // // Given the pdo, retrieve the devnode (the device instance string is // attached to the devnode in the InstancePath field). // deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; if (!deviceNode) { ObDereferenceObject(DeviceObject); return STATUS_INVALID_PARAMETER_1; } // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (because of the first index into // the DeviceIdVetoNameBuffer, this is double null terminated). // for(vetoData = MultiSzVetoList; *vetoData; vetoData += vetoListLength) { vetoListLength = (ULONG)(wcslen(vetoData) + 1); } vetoListLength = ((ULONG)(vetoData - MultiSzVetoList))*sizeof(WCHAR); dataSize = sizeof(PLUGPLAY_EVENT_BLOCK) + deviceNode->InstancePath.Length + vetoListLength + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; deviceEvent->Data.u.VetoNotification.VetoType = VetoType; // // You can think of this as a MultiSz string where the first entry is the // DeviceId for the device being removed, and the next Id's all corrospond // to the vetoers. // RtlCopyMemory( deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer, deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length ); i = deviceNode->InstancePath.Length; deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)] = UNICODE_NULL; vetoData = (&deviceEvent->Data.u.VetoNotification.DeviceIdVetoNameBuffer[i/sizeof(WCHAR)])+1; RtlCopyMemory(vetoData, MultiSzVetoList, vetoListLength); vetoData[vetoListLength/sizeof(WCHAR)] = UNICODE_NULL; // // No need to NULL terminate the entry after the last one as we prezero'd // the buffer. Now set the appropriate GUID so the UI looks right. // if (PiCompareGuid(&VetoedDeviceEvent->Data.EventGuid, &GUID_DEVICE_EJECT)) { deviceEvent->Data.EventGuid = GUID_DEVICE_EJECT_VETOED; } else { ASSERT(PiCompareGuid(&VetoedDeviceEvent->Data.EventGuid, &GUID_DEVICE_QUERY_AND_REMOVE)); deviceEvent->Data.EventGuid = GUID_DEVICE_REMOVAL_VETOED; } deviceEvent->Data.EventCategory = VetoEvent; status = PiNotifyUserMode(deviceEvent); ExFreePool(deviceEvent); ObDereferenceObject(DeviceObject); return status; } NTSTATUS PpNotifyUserModeRemovalSafe( IN PDEVICE_OBJECT DeviceObject ) /*++ Routine Description: This routine is called to notify user mode a device can be removed. This is similar to PpSetDeviceRemovalSafe except that we are already in a kernel mode PnP device event we must complete, from which this function will piggyback a notification up to just user mode. Arguments: DeviceObject - Indicates the device object is ready for removal. Return Value: None. --*/ { NTSTATUS status; PDEVICE_NODE deviceNode; ULONG dataSize, totalSize, i; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); ASSERT(DeviceObject != NULL); // // Reference the device object so it can't go away until after we're // done with notification. // ObReferenceObject(DeviceObject); deviceNode = (PDEVICE_NODE)DeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(deviceNode); // // Calculate the size of the PLUGPLAY_EVENT_BLOCK, this is the size that // we record in the TotalSize field later (the length doesn't count the // terminating null but we're already counting the first index into the // DeviceId field so it works out. Add one more for double-null term. // dataSize = sizeof(PLUGPLAY_EVENT_BLOCK); dataSize += deviceNode->InstancePath.Length + sizeof(WCHAR); totalSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + dataSize; deviceEvent = ExAllocatePoolWithTag(PagedPool, totalSize, PNP_DEVICE_EVENT_ENTRY_TAG); if (deviceEvent == NULL) { ObDereferenceObject(DeviceObject); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)deviceEvent, totalSize); deviceEvent->CallerEvent = NULL; deviceEvent->Argument = 0; deviceEvent->VetoType = NULL; deviceEvent->VetoName = NULL; deviceEvent->Data.EventGuid = GUID_DEVICE_SAFE_REMOVAL; deviceEvent->Data.EventCategory = TargetDeviceChangeEvent; deviceEvent->Data.Result = 0; deviceEvent->Data.Flags = 0; deviceEvent->Data.TotalSize = dataSize; deviceEvent->Data.DeviceObject = (PVOID)DeviceObject; if (deviceNode->InstancePath.Length != 0) { RtlCopyMemory((PVOID)deviceEvent->Data.u.TargetDevice.DeviceIds, (PVOID)deviceNode->InstancePath.Buffer, deviceNode->InstancePath.Length); } i = deviceNode->InstancePath.Length/sizeof(WCHAR); deviceEvent->Data.u.TargetDevice.DeviceIds[i] = L'\0'; status = PiNotifyUserMode(deviceEvent); ExFreePool(deviceEvent); ObDereferenceObject(DeviceObject); return status; } // PpNotifyUserModeRemovalSafe NTSTATUS PiProcessQueryRemoveAndEject( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ) /*++ Routine Description: This routine processes the various flavours of remove: Eject, SurpriseRemove, Remove, and QueryRemove. Eject Retrieve bus, removal, and eject relations. Do queries on all relations Send IRP_MN_REMOVE_DEVICE to all the relations. Queue the pending eject Once the eject happens Reenumerate all the indirect relation's parents SurpriseRemove Retrieve bus, removal, and eject relations. Send IRP_MN_SURPRISE_REMOVAL to all the direct relations. Notify everyone that the device is gone. Reenumerate the parents of all the indirect relations. Remove the indirect relations from the relations list. Queue the pending surprise removal. Once the last handle is closed. Send IRP_MN_REMOVE_DEVICE to all the direct relations. RemoveFailedDevice Retrieve bus and removal relations. Notify everyone that the device is going. Reenumerate the parents of all the indirect relations. Remove the indirect relations from the relations list. Queue as a pending surprise removal. Once the last handle is closed. Send IRP_MN_REMOVE_DEVICE to all the direct relations. RemoveUnstartedFailedDevice Retrieve bus relations. Notify everyone that the device is going. Send IRP_MN_REMOVE_DEVICE to all the direct relations. Remove Use the relations from the last QueryRemove or retrieve new bus, removal, and eject relations if the device wasn't already QueryRemoved. Send IRP_MN_REMOVE_DEVICE to all the relations. Arguments: Response - Result of event processing in user-mode. Return Value: NTSTATUS code. --*/ { PPNP_DEVICE_EVENT_ENTRY deviceEvent; PPNP_DEVICE_EVENT_ENTRY surpriseRemovalEvent; PLUGPLAY_DEVICE_DELETE_TYPE deleteType; PPENDING_RELATIONS_LIST_ENTRY pendingRelations; PNP_VETO_TYPE vetoType; PDEVICE_OBJECT deviceObject, relatedDeviceObject; PDEVICE_OBJECT *pdoList; PDEVICE_NODE deviceNode, relatedDeviceNode; PRELATION_LIST relationsList; ULONG relationCount; NTSTATUS status; ULONG marker; BOOLEAN directDescendant; PDEVICE_OBJECT vetoingDevice = NULL; PDRIVER_OBJECT vetoingDriver = NULL; LONG index; BOOLEAN possibleProfileChangeInProgress = FALSE; BOOLEAN subsumingProfileChange = FALSE; BOOLEAN hotEjectSupported; BOOLEAN warmEjectSupported; BOOLEAN excludeIndirectRelations; UNICODE_STRING singleVetoListItem; PWSTR vetoList; UNICODE_STRING internalVetoString; PWSTR internalVetoBuffer; PDOCK_INTERFACE dockInterface = NULL; PAGED_CODE(); deviceEvent = *DeviceEvent; deviceObject = (PDEVICE_OBJECT)deviceEvent->Data.DeviceObject; deviceNode = deviceObject->DeviceObjectExtension->DeviceNode; surpriseRemovalEvent = NULL; PpDevNodeLockTree(PPL_TREEOP_ALLOW_READS); if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_EJECT)) { deleteType = EjectDevice; } else if (deviceEvent->Data.Flags & TDF_KERNEL_INITIATED) { if (!(deviceNode->Flags & DNF_ENUMERATED)) { ASSERT(deviceNode->State == DeviceNodeAwaitingQueuedDeletion); if ((deviceNode->PreviousState == DeviceNodeStarted) || (deviceNode->PreviousState == DeviceNodeStopped) || (deviceNode->PreviousState == DeviceNodeStartPostWork) || (deviceNode->PreviousState == DeviceNodeRestartCompletion)) { deleteType = SurpriseRemoveDevice; } else { deleteType = RemoveDevice; } } else { ASSERT(deviceNode->State == DeviceNodeAwaitingQueuedRemoval); if ((deviceNode->PreviousState == DeviceNodeStarted) || (deviceNode->PreviousState == DeviceNodeStopped) || (deviceNode->PreviousState == DeviceNodeStartPostWork) || (deviceNode->PreviousState == DeviceNodeRestartCompletion)) { deleteType = RemoveFailedDevice; } else { deleteType = RemoveUnstartedFailedDevice; } } } else { deleteType = QueryRemoveDevice; } IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Remove type %x\n", deleteType)); if (deleteType == QueryRemoveDevice || deleteType == EjectDevice) { if (deviceNode->Flags & DNF_LEGACY_DRIVER) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed by legacy driver\n")); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoLegacyDevice, &deviceNode->InstancePath ); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } if (deleteType == QueryRemoveDevice && deviceEvent->Argument == CM_PROB_DISABLED) { // // if we're trying to remove the device to disable the device // if (deviceNode->DisableableDepends > 0) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Device is non-disableable\n")); // // we should have caught this before (in usermode PnP) // but a rare scenario can exist where the device becomes non-disableable // There is still a potential gap, if the device hasn't got around // to marking itself as non-disableable yet // PiFinalizeVetoedRemove( deviceEvent, PNP_VetoNonDisableable, &deviceNode->InstancePath ); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } // // Allocate room for a possible veto buffer. // internalVetoBuffer = (PWSTR) PiAllocateCriticalMemory( deleteType, PagedPool, MAX_VETO_NAME_LENGTH * sizeof(WCHAR), 'rcpP' ); if (internalVetoBuffer == NULL) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to failure to allocate vetobuffer\n")); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoTypeUnknown, NULL ); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } // // Preinit the veto information // vetoType = PNP_VetoTypeUnknown; internalVetoString.MaximumLength = MAX_VETO_NAME_LENGTH; internalVetoString.Length = 0; internalVetoString.Buffer = internalVetoBuffer; if (deleteType == EjectDevice) { if (deviceNode->Flags & DNF_LOCKED_FOR_EJECT) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Device already being ejected\n")); // // Either this node or one of its ancestors is already being ejected. // ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_SUCCESS; } if (deviceEvent->Data.Flags & TDF_KERNEL_INITIATED) { // // Check permissions. // status = PiNotifyUserModeKernelInitiatedEject( deviceObject, &vetoType, &internalVetoString ); if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Kernel initiated eject vetoed by user mode\n")); PiFinalizeVetoedRemove( deviceEvent, vetoType, &internalVetoString ); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } if ((deviceNode->DockInfo.DockStatus == DOCK_DEPARTING) || (deviceNode->DockInfo.DockStatus == DOCK_EJECTIRP_COMPLETED)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Dock already being ejected\n")); // // We already have an eject queued against this device. Don't allow // another eject to break into the middle of a queue/cancel warm // eject sequence. // ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_SUCCESS; } // // What types of ejection can we do? (warm/hot) // if (!IopDeviceNodeFlagsToCapabilities(deviceNode)->Removable) { // // This device is neither ejectable, nor even removable. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Device not removable\n")); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoIllegalDeviceRequest, &deviceNode->InstancePath ); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } if ((deleteType == QueryRemoveDevice) && (!PipAreDriversLoaded(deviceNode))) { // // The device doesn't have an FDO. // status = STATUS_SUCCESS; if ((deviceNode->State == DeviceNodeInitialized) || (deviceNode->State == DeviceNodeRemoved)) { // // The rules are: // 1) !TDF_NO_RESTART means clear the devnode and get it ready // as long as the problem is user resettable. Ignore the passed // in problem code (probably either CM_PROB_WILL_BE_REMOVED or // CM_PROB_DEVICE_NOT_THERE), as it means nothing. // 2) TDF_NO_RESTART means change the problem code over if you can. // If the problem code is not user resettable, the problem code // won't change. // // // In all cases we try to clear the problem. // if (PipDoesDevNodeHaveProblem(deviceNode)) { if (!PipIsProblemReadonly(deviceNode->Problem)) { PipClearDevNodeProblem(deviceNode); } } if (!PipDoesDevNodeHaveProblem(deviceNode)) { if (!(deviceEvent->Data.Flags & TDF_NO_RESTART)) { // // This is a reset attempt. Mark the devnode so that it // comes online next enumeration. // IopRestartDeviceNode(deviceNode); } else { // // We're changing or setting problem codes. Note that the // device is still in DeviceNodeInitialized or // DeviceNodeRemoved. // PipSetDevNodeProblem(deviceNode, deviceEvent->Argument); } } else { // // The problem is fixed, so the devnode state is immutable // as far as user mode is concerned. Here we fail the call // if we can't bring the devnode back online. We always succeed // the call if it was an attempt to change the code, as the // user either wants to prepare the device for ejection (done), // or wants to disable it (as good as done.) // if (!(deviceEvent->Data.Flags & TDF_NO_RESTART)) { status = STATUS_INVALID_PARAMETER; } } } PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); ExFreePool(internalVetoBuffer); return status; } status = IopBuildRemovalRelationList( deviceObject, deleteType, &vetoType, &internalVetoString, &relationsList); if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Failed to build removal relations\n")); PiFinalizeVetoedRemove( deviceEvent, vetoType, &internalVetoString ); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } ASSERT(relationsList != NULL); // // Resize the event buffer and add these device instance strings to the // list to notify. // relationCount = IopGetRelationsCount( relationsList ); ASSERT(!IopGetRelationsTaggedCount( relationsList )); // // PdoList will become a list of devices that must be queried. This is // a subset of all the devices that might disappear, all of which appear // in the relations list. // pdoList = (PDEVICE_OBJECT *) PiAllocateCriticalMemory( deleteType, NonPagedPool, relationCount * sizeof(PDEVICE_OBJECT), 'rcpP' ); if (pdoList != NULL) { relationCount = 0; marker = 0; while (IopEnumerateRelations( relationsList, &marker, &relatedDeviceObject, &directDescendant, NULL, TRUE)) { // // Here is a list of what operations retrieve what relations, // who they query, and who/how they notify. // // Operation Relations Queries Notifies // --------- --------- ------- -------- // EjectDevice Ejection Everyone Everyone (Remove) // SurpriseRemoveDevice Ejection NA Descendants (SurpriseRemove) // RemoveDevice Ejection NA Descendants (Remove) // RemoveFailedDevice Removal NA Descendants (SurpriseRemove) // RemoveUnstartedFailedDevice Removal NA Descendants (Remove) // QueryRemoveDevice Removal Everyone Everyone (Remove) // // // N.B. // We do not send SurpriseRemove's to removal relations. // While doing so might seem to be the correct behavior, many // drivers do not handle this well. Simply reenumerating the // parents of the removal relations works much better. Similarly // ejection relations have their parents reenumerated (which // makes sense, as they are speculative in nature anyway). // // If we get in a case where a *parent* of a dock gets // into the RemoveFailedDevice case (ie, failed restart, // responded to QueryDeviceState with Removed, etc), then we // will be shortly losing the children when we stop the parent. // However, we want to eject the dock child, not just remove it // as starting and ejecting are symmetric here. Note that // currently the only such parent would be the root ACPI devnode. // // Ejection relations of a device (eg dock) that has been // surprise removed are not notified that they *may* have been // pulled (remember, ejection relations are speculative). We // will notify only DirectDescendants and queue an enumeration // on every parent of the ejection relations. If they really // disappeared, they will get their notification, albeit a bit // later than some of the other devices in the tree. // if (directDescendant || deleteType == EjectDevice || deleteType == QueryRemoveDevice) { relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; // // PiProcessQueryRemoveAndEject will be called twice for // the dock during an eject. Once with EjectDevice, and // after the dock is listed as missing once more with // RemoveDevice. We don't want to start a profile change // for RemoveDevice as we are already in one, and we would // deadlock if we tried. We don't start one for QueryRemove // either as the dock isn't *physically* going away. // ASSERT(relatedDeviceNode->DockInfo.DockStatus != DOCK_ARRIVING); if (deleteType != RemoveDevice && deleteType != QueryRemoveDevice) { if (relatedDeviceNode->DockInfo.DockStatus == DOCK_QUIESCENT) { possibleProfileChangeInProgress = TRUE; } else if (relatedDeviceNode->DockInfo.DockStatus != DOCK_NOTDOCKDEVICE) { subsumingProfileChange = TRUE; } } relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if (deleteType == QueryRemoveDevice || deleteType == EjectDevice) { if (relatedDeviceNode->Flags & DNF_LEGACY_DRIVER) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed by legacy driver relation\n")); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoLegacyDevice, &relatedDeviceNode->InstancePath ); status = STATUS_UNSUCCESSFUL; break; } if (relatedDeviceNode->State == DeviceNodeRemovePendingCloses) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to device in DeviceNodeRemovePendingCloses\n")); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoOutstandingOpen, &relatedDeviceNode->InstancePath ); status = STATUS_UNSUCCESSFUL; break; } } pdoList[ relationCount++ ] = relatedDeviceObject; } } } else { status = STATUS_INSUFFICIENT_RESOURCES; } if (NT_SUCCESS(status)) { if ((deleteType == SurpriseRemoveDevice || deleteType == RemoveFailedDevice || deleteType == RemoveUnstartedFailedDevice || deleteType == RemoveDevice)) { excludeIndirectRelations = TRUE; } else { excludeIndirectRelations = FALSE; } status = PiResizeTargetDeviceBlock( DeviceEvent, deleteType, relationsList, excludeIndirectRelations ); deviceEvent = *DeviceEvent; if (deleteType == SurpriseRemoveDevice) { PiBuildUnsafeRemovalDeviceBlock( deviceEvent, relationsList, &surpriseRemovalEvent ); } } if (!NT_SUCCESS(status)) { IopFreeRelationList(relationsList); if (pdoList) { ExFreePool(pdoList); } ExFreePool(internalVetoBuffer); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoTypeUnknown, NULL ); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return status; } // // We may need to take the hardware profile change semaphore, and also // broadcast a hardware profile change request... // if (possibleProfileChangeInProgress) { PpProfileBeginHardwareProfileTransition(subsumingProfileChange); // // Walk the list of docks who are going to disappear and mark them as // in profile transition. // for (index = relationCount - 1; index >= 0; index--) { relatedDeviceObject = pdoList[ index ]; relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; ASSERT(relatedDeviceNode->DockInfo.DockStatus != DOCK_ARRIVING); if (relatedDeviceNode->DockInfo.DockStatus == DOCK_QUIESCENT) { PpProfileIncludeInHardwareProfileTransition( relatedDeviceNode, DOCK_DEPARTING ); } } // // We can only be in one of the following deleteType situations // // 1) EjectDevice - Good, normal ejection request by our user // (we are using EjectionRelations) // // 2) SurpriseRemoveDevice - Someone yanked the dock out. // (we are using EjectionRelations) // // 3) RemoveFailedDevice - A start failed after a stop on a parent or // even our device. This case is not handled // correctly. We assert for now, and we // maroon the dock, ie lose it's devnode but // the dock stays physically present and is // in the users eye's unejectable. // // 4) RemoveDevice - This occurs in three cases: // a) A removed device is disappearing. // b) A device is being removed but has not // been started. // c) A device has failed start. // // We pass through case a) during a normal // ejection and as part of a profile // transition begun earlier. c) is similar // to a) but the transition was begun by the // start code. For case b) we don't want to // turn it into an eject, as the OS might be // removing our parent as a normal part of // setup, and we wouldn't want to undock then // (and we probably aren't changing profiles // anyway). // // 5) QueryRemoveDevice This should never be the case here per the // explicit veto in the IopEnumerateRelations // code above. // // // RemoveFailedDevice is a PathTrap - the only parent of a dock is // the ACPI root devnode right now. We shouldn't get into that case. // ASSERT(deleteType != QueryRemoveDevice && deleteType != RemoveFailedDevice); if (deleteType == EjectDevice) { // // Are there any legacy drivers in the system? // status = IoGetLegacyVetoList(&vetoList, &vetoType); if (NT_SUCCESS(status) && (vetoType != PNP_VetoTypeUnknown)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to presence of a legacy driver\n")); // // Release any docks in profile transition // PpProfileCancelHardwareProfileTransition(); IopFreeRelationList(relationsList); // // Failure occured, notify user mode as appropriate, or fill in // the veto buffer. // if (deviceEvent->VetoType != NULL) { *deviceEvent->VetoType = vetoType; } if (deviceEvent->VetoName == NULL) { // // If there is not a VetoName passed in then call user mode // to display the eject veto notification to the user. // PiNotifyUserModeRemoveVetoedByList( deviceEvent, deviceObject, vetoType, vetoList ); } else { // // The veto data in the PNP_DEVICE_EVENT_ENTRY block is // a UNICODE_STRING field. Since that type of data structure // cannot handle Multi-Sz data, we cull the information down // to one entry here. // RtlCopyUnicodeString(deviceEvent->VetoName, &singleVetoListItem); RtlInitUnicodeString(&singleVetoListItem, vetoList); } ExFreePool(vetoList); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } // // Broadcast the query for a profile change against our current // list of docks in transition... // status = PpProfileQueryHardwareProfileChange( subsumingProfileChange, PROFILE_IN_PNPEVENT, &vetoType, &internalVetoString ); if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to failed HW profile change\n")); // // Release any docks in profile transition // PpProfileCancelHardwareProfileTransition(); IopFreeRelationList(relationsList); PiFinalizeVetoedRemove( deviceEvent, vetoType, &internalVetoString ); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } } if (deleteType == QueryRemoveDevice || deleteType == EjectDevice) { // // Send query notification to user-mode. // status = PiNotifyUserModeDeviceRemoval( deviceEvent, &GUID_TARGET_DEVICE_QUERY_REMOVE, &vetoType, &internalVetoString ); if (NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: QUERY_REMOVE - notifying kernel-mode\n")); // // Send query notification to kernel-mode drivers. // for (index = 0; index < (LONG)relationCount; index++) { relatedDeviceObject = pdoList[ index ]; status = IopNotifyTargetDeviceChange( &GUID_TARGET_DEVICE_QUERY_REMOVE, relatedDeviceObject, NULL, &vetoingDriver); if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to driver failing QR notification\n")); vetoType = PNP_VetoDriver; if (vetoingDriver != NULL) { RtlCopyUnicodeString(&internalVetoString, &vetoingDriver->DriverName); } else { RtlInitUnicodeString(&internalVetoString, NULL); } for (index--; index >= 0; index--) { relatedDeviceObject = pdoList[ index ]; IopNotifyTargetDeviceChange( &GUID_TARGET_DEVICE_REMOVE_CANCELLED, relatedDeviceObject, NULL, NULL); } break; } } if (NT_SUCCESS(status)) { // // If we haven't already performed the action yet (a query remove // to the target device, in this case), then do it now. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: QueryRemove DevNodes\n")); status = IopDeleteLockedDeviceNodes(deviceObject, relationsList, QueryRemoveDevice, TRUE, 0, &vetoType, &internalVetoString); if (NT_SUCCESS(status)) { // // Everyone has been notified and had a chance to close their handles. // Since no one has vetoed it yet, let's see if there are any open // references. // if (IopNotifyPnpWhenChainDereferenced( pdoList, relationCount, TRUE, &vetoingDevice )) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to open handles against the device\n")); vetoType = PNP_VetoOutstandingOpen; if (!PiCollectOpenHandles(pdoList, relationCount, FALSE, &internalVetoString)) { if (vetoingDevice != NULL) { relatedDeviceNode = (PDEVICE_NODE)vetoingDevice->DeviceObjectExtension->DeviceNode; ASSERT(relatedDeviceNode != NULL); RtlCopyUnicodeString(&internalVetoString, &relatedDeviceNode->InstancePath); } else { RtlInitUnicodeString(&internalVetoString, NULL); } } // // Send cancel remove to the target devices. // IopDeleteLockedDeviceNodes(deviceObject, relationsList, CancelRemoveDevice, TRUE, 0, NULL, NULL); status = STATUS_UNSUCCESSFUL; } } else if (vetoType == PNP_VetoDevice) { PiCollectOpenHandles(pdoList, relationCount, FALSE, &internalVetoString); } if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due someone in the stack failed QR\n")); // // Send cancel notification to kernel-mode drivers. // for (index = relationCount - 1; index >= 0; index--) { relatedDeviceObject = pdoList[ index ]; IopNotifyTargetDeviceChange( &GUID_TARGET_DEVICE_REMOVE_CANCELLED, relatedDeviceObject, NULL, NULL); } } } if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_IOEVENT_WARNING_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed by \"%wZ\" (type 0x%x)\n", &internalVetoString, vetoType)); PiFinalizeVetoedRemove( deviceEvent, vetoType, &internalVetoString ); // // A driver vetoed the query remove, go back and send // cancels to user-mode (cancels already sent to drivers // that received the query). // PiNotifyUserModeDeviceRemoval( deviceEvent, &GUID_TARGET_DEVICE_REMOVE_CANCELLED, NULL, NULL ); } } else { IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed due to UM failing QR\n")); PiFinalizeVetoedRemove( deviceEvent, vetoType, &internalVetoString ); } if (!NT_SUCCESS(status)) { // // Broadcast a cancel HW profile change event if appropriate. // if (possibleProfileChangeInProgress) { // // Release any docks in profile transition. We also broadcast // the cancel. // PpProfileCancelHardwareProfileTransition(); } // // User-mode vetoed the request (cancels already sent // to user-mode callers that received the query). // IopFreeRelationList(relationsList); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } } else if (deleteType == SurpriseRemoveDevice || deleteType == RemoveFailedDevice) { // // Send IRP_MN_SURPRISE_REMOVAL, IopDeleteLockDeviceNodes ignores // indirect descendants for SurpriseRemoveDevice. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: QueryRemove DevNodes\n")); IopDeleteLockedDeviceNodes( deviceObject, relationsList, SurpriseRemoveDevice, FALSE, 0, NULL, NULL); } // // Notify user-mode and drivers that a remove is happening. User-mode // sees this as a remove pending if it's user initiated, we don't give // them the "remove" until it's actually gone. // if (deleteType != SurpriseRemoveDevice) { // // ISSUE - 2000/08/20 - ADRIAO: Busted message path // We send GUID_DEVICE_REMOVE_PENDING to devices that are already // dead in the case of RemoveFailedDevice. // PiNotifyUserModeDeviceRemoval( deviceEvent, &GUID_DEVICE_REMOVE_PENDING, NULL, NULL ); } else { if (surpriseRemovalEvent) { PiNotifyUserModeDeviceRemoval( surpriseRemovalEvent, &GUID_DEVICE_SURPRISE_REMOVAL, NULL, NULL ); ExFreePool(surpriseRemovalEvent); } PiNotifyUserModeDeviceRemoval( deviceEvent, &GUID_TARGET_DEVICE_REMOVE_COMPLETE, NULL, NULL ); } IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: REMOVE_COMPLETE - notifying kernel-mode\n")); for (index = 0; index < (LONG)relationCount; index++) { relatedDeviceObject = pdoList[ index ]; status = IopNotifyTargetDeviceChange( &GUID_TARGET_DEVICE_REMOVE_COMPLETE, relatedDeviceObject, NULL, NULL); ASSERT(NT_SUCCESS(status)); } if (deleteType == RemoveDevice || deleteType == RemoveFailedDevice || deleteType == SurpriseRemoveDevice) { // // For these operations indirect relations are speculative. // // So for each of the indirect relations, invalidate their parents and // remove them from the relations list. // IopInvalidateRelationsInList( relationsList, deleteType, TRUE, FALSE ); IopRemoveIndirectRelationsFromList( relationsList ); } if (deleteType == RemoveFailedDevice || deleteType == SurpriseRemoveDevice) { // // We've sent the surprise remove IRP to the original device and all its // direct descendants. We've also notified user-mode. // // // Unlock the device relations list. // // Note there could be a potential race condition here between // unlocking the devnodes in the relation list and completing the // execution of IopNotifyPnpWhenChainDereferenced. If an enumeration // takes places (we've unlocked the devnode) before the eventual remove // is sent then problems could occur. // // This is prevented by the setting of DNF_REMOVE_PENDING_CLOSES when // we sent the IRP_MN_SURPRISE_REMOVAL. // // We do need to do it prior to calling IopQueuePendingSurpriseRemoval // since we lose ownership of the relation list in that call. Also // IopNotifyPnpWhenChainDereferenced may cause the relation list to be // freed before it returns. // // If this is a RemoveFailedDevice then we don't want to remove the // device node from the tree but we do want to remove children without // resources. // IopUnlinkDeviceRemovalRelations( deviceObject, relationsList, deleteType == SurpriseRemoveDevice ? UnlinkAllDeviceNodesPendingClose : UnlinkOnlyChildDeviceNodesPendingClose); // // Add the relation list to a list of pending surprise removals. // IopQueuePendingSurpriseRemoval( deviceObject, relationsList, deviceEvent->Argument ); // // Release the engine lock *before* IopNotifyPnpWhenChainDereferenced, // as it may call back into us... // PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); IopNotifyPnpWhenChainDereferenced( pdoList, relationCount, FALSE, NULL ); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); return STATUS_SUCCESS; } if (deviceNode->DockInfo.DockStatus != DOCK_NOTDOCKDEVICE) { status = IopQueryDockRemovalInterface( deviceObject, &dockInterface ); if (dockInterface) { // // Make sure updates don't occur on removes during an ejection. // We may change this to PDS_UPDATE_ON_EJECT *after* the remove // IRPs go through (as only then do we know our power // constraints) // dockInterface->ProfileDepartureSetMode( dockInterface->Context, PDS_UPDATE_ON_INTERFACE ); } } // // Send the remove to the devnode tree. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessQueryRemoveAndEject: RemoveDevice DevNodes\n")); status = IopDeleteLockedDeviceNodes(deviceObject, relationsList, RemoveDevice, (BOOLEAN)(deleteType == QueryRemoveDevice || deleteType == EjectDevice), deviceEvent->Argument, NULL, NULL); hotEjectSupported = (BOOLEAN) IopDeviceNodeFlagsToCapabilities(deviceNode)->EjectSupported; warmEjectSupported = (BOOLEAN) IopDeviceNodeFlagsToCapabilities(deviceNode)->WarmEjectSupported; if (deleteType != EjectDevice) { if (!(deviceEvent->Data.Flags & TDF_NO_RESTART)) { // // Set a flag to let kernel-mode know we'll be wanting to // restart these devnodes, eventually. // marker = 0; while (IopEnumerateRelations( relationsList, &marker, &relatedDeviceObject, NULL, NULL, TRUE)) { relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if (relatedDeviceNode && relatedDeviceNode->State == DeviceNodeRemoved && PipIsDevNodeProblem(relatedDeviceNode, CM_PROB_WILL_BE_REMOVED)) { if (!(deviceEvent->Data.Flags & TDF_ONLY_RESTART_RELATIONS) || deviceObject != relatedDeviceObject) { PipClearDevNodeProblem(relatedDeviceNode); IopRestartDeviceNode(relatedDeviceNode); } } } } // // Unlock the device relations list. // IopUnlinkDeviceRemovalRelations( deviceObject, relationsList, UnlinkRemovedDeviceNodes ); IopFreeRelationList(relationsList); } else if (hotEjectSupported || warmEjectSupported) { // // From this point on we cannot return any sort of failure without // going through IopEjectDevice or cancelling any outstanding profile // change. // // // Set a flag to let kernel-mode know we'll be wanting to // restart these devnodes, eventually. // marker = 0; while (IopEnumerateRelations( relationsList, &marker, &relatedDeviceObject, NULL, NULL, TRUE)) { relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if (relatedDeviceNode) { relatedDeviceNode->Flags |= DNF_LOCKED_FOR_EJECT; } } IopUnlinkDeviceRemovalRelations( deviceObject, relationsList, UnlinkRemovedDeviceNodes ); // // Send the eject // pendingRelations = ExAllocatePool( NonPagedPool, sizeof(PENDING_RELATIONS_LIST_ENTRY) ); if (pendingRelations == NULL) { // // It's cleanup time. Free up everything that matters // if (dockInterface) { dockInterface->ProfileDepartureSetMode( dockInterface->Context, PDS_UPDATE_DEFAULT ); dockInterface->InterfaceDereference(dockInterface->Context); } ExFreePool(pdoList); ExFreePool(internalVetoBuffer); if (possibleProfileChangeInProgress) { // // Release any docks in profile transition. We also broadcast // the cancel. // PpProfileCancelHardwareProfileTransition(); } // // This will bring back online the devices that were held offline // for the duration of the undock. // IopInvalidateRelationsInList(relationsList, deleteType, FALSE, TRUE); // // Free the relations list // IopFreeRelationList(relationsList); // // Let the user know we were unable to process the request. // PiFinalizeVetoedRemove( deviceEvent, PNP_VetoTypeUnknown, NULL ); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } // // Fill out the pending eject information. // ObReferenceObject(deviceObject); pendingRelations->DeviceEvent = deviceEvent; pendingRelations->DeviceObject = deviceObject; pendingRelations->RelationsList = relationsList; pendingRelations->ProfileChangingEject = possibleProfileChangeInProgress; pendingRelations->DisplaySafeRemovalDialog = (BOOLEAN)(deviceEvent->VetoName == NULL); pendingRelations->DockInterface = dockInterface; // // Now that we've removed all the devices that won't be present // in the new hardware profile state (eg batteries, etc), // status = PoGetLightestSystemStateForEject( possibleProfileChangeInProgress, hotEjectSupported, warmEjectSupported, &pendingRelations->LightestSleepState ); if (!NT_SUCCESS(status)) { if (status == STATUS_INSUFFICIENT_POWER) { PiFinalizeVetoedRemove( deviceEvent, PNP_VetoInsufficientPower, NULL ); } else { IopDbgPrint((IOP_IOEVENT_WARNING_LEVEL, "PiProcessQueryRemoveAndEject: Vetoed by power system (%x)\n", status)); PiFinalizeVetoedRemove( deviceEvent, PNP_VetoTypeUnknown, NULL ); } // // We'll complete this one ourselves thank you. // pendingRelations->DeviceEvent = NULL; pendingRelations->DisplaySafeRemovalDialog = FALSE; // // Release any profile transitions. // InitializeListHead( &pendingRelations->Link ); IopProcessCompletedEject((PVOID) pendingRelations); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_PLUGPLAY_QUERY_VETOED; } PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); // // Completion routine for the eject IRP handles display of the // safe removal dialog and completion of the event. Returning // STATUS_PENDING does let other events get processed though. // IopEjectDevice( deviceObject, pendingRelations ); ExFreePool(pdoList); ExFreePool(internalVetoBuffer); return STATUS_PENDING; } else { // // All docks must be hot or warm ejectable. // ASSERT(!dockInterface); // // Unlock the device relations list. // IopUnlinkDeviceRemovalRelations( deviceObject, relationsList, UnlinkRemovedDeviceNodes ); IopFreeRelationList(relationsList); // // This hardware supports neither hot nor warm eject, but it is // removable. It can therefore be thought of as a "user assisted" hot // eject. In this case we do *not* want to wait around for the user to // "complete the eject" and then put up the message. So we piggyback a // safe removal notification while UmPnPMgr is alert and waiting in // user mode, and the user gets the dialog now. // if (deviceEvent->VetoName == NULL) { PpNotifyUserModeRemovalSafe(deviceObject); } } if (deleteType == RemoveDevice) { // // Notify user-mode one last time that everything is actually done. // PiNotifyUserModeDeviceRemoval( deviceEvent, &GUID_TARGET_DEVICE_REMOVE_COMPLETE, NULL, NULL ); } ExFreePool(pdoList); if (dockInterface) { dockInterface->ProfileDepartureSetMode( dockInterface->Context, PDS_UPDATE_DEFAULT ); dockInterface->InterfaceDereference(dockInterface->Context); } ExFreePool(internalVetoBuffer); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); return STATUS_SUCCESS; } NTSTATUS PiProcessTargetDeviceEvent( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ) /*++ Routine Description: This routine processes each type of event in the target device category. These events may have been initiated by either user-mode or kernel mode. Arguments: deviceEvent - Data describing the type of target device event and the target device itself. Return Value: None. --*/ { NTSTATUS status = STATUS_SUCCESS; PPNP_DEVICE_EVENT_ENTRY deviceEvent; PAGED_CODE(); deviceEvent = *DeviceEvent; IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessTargetDeviceEvent: Entered\n")); //----------------------------------------------------------------- // QUERY and REMOVE //----------------------------------------------------------------- if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_QUERY_AND_REMOVE)) { status = PiProcessQueryRemoveAndEject(DeviceEvent); } //----------------------------------------------------------------- // EJECT //----------------------------------------------------------------- else if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_EJECT)) { status = PiProcessQueryRemoveAndEject(DeviceEvent); } //----------------------------------------------------------------- // ARRIVAL //----------------------------------------------------------------- else if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_ARRIVAL)) { // // Notify user-mode (not drivers) that an arrival just happened. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessTargetDeviceEvent: ARRIVAL - notifying user-mode\n")); PiNotifyUserMode(deviceEvent); } //----------------------------------------------------------------- // NO-OP REQUEST (to flush device event queue) //----------------------------------------------------------------- else if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_NOOP)) { status = STATUS_SUCCESS; } //----------------------------------------------------------------- // SAFE REMOVAL NOTIFICATION //----------------------------------------------------------------- else if (PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_DEVICE_SAFE_REMOVAL)) { // // Notify user-mode (and nobody else) that it is now safe to remove // someone. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessTargetDeviceEvent: SAFE_REMOVAL - notifying user-mode\n")); PiNotifyUserMode(deviceEvent); } return status; } // PiProcessTargetDeviceEvent NTSTATUS PiProcessCustomDeviceEvent( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent ) /*++ Routine Description: This routine processes each type of event in the custom device category. These events may have been initiated by either user-mode or kernel mode. Arguments: deviceEvent - Data describing the type of custom device event and the target device itself. Return Value: None. --*/ { PPNP_DEVICE_EVENT_ENTRY deviceEvent; PTARGET_DEVICE_CUSTOM_NOTIFICATION customNotification; PDEVICE_OBJECT deviceObject; PAGED_CODE(); deviceEvent = *DeviceEvent; IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessCustomDeviceEvent: Entered\n")); ASSERT(PiCompareGuid(&deviceEvent->Data.EventGuid, &GUID_PNP_CUSTOM_NOTIFICATION)); deviceObject = (PDEVICE_OBJECT)deviceEvent->Data.DeviceObject; customNotification = (PTARGET_DEVICE_CUSTOM_NOTIFICATION)deviceEvent->Data.u.CustomNotification.NotificationStructure; // // Notify user-mode that something just happened. // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessCustomDeviceEvent: CUSTOM_NOTIFICATION - notifying user-mode\n")); PiNotifyUserMode(deviceEvent); // // Notify K-mode // IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PiProcessCustomDeviceEvent: CUSTOM_NOTIFICATION - notifying kernel-mode\n")); IopNotifyTargetDeviceChange( &customNotification->Event, deviceObject, customNotification, NULL); return STATUS_SUCCESS; } // PiProcessCustomDeviceEvent NTSTATUS PiResizeTargetDeviceBlock( IN OUT PPNP_DEVICE_EVENT_ENTRY *DeviceEvent, IN PLUGPLAY_DEVICE_DELETE_TYPE DeleteType, IN PRELATION_LIST RelationsList, IN BOOLEAN ExcludeIndirectRelations ) /*++ Routine Description: This routine takes the passed in device event block and resizes it to hold a multisz list of device instance strings in the DeviceIds field. This list includes the original target device id plus the device id for all the device objects in the specified DeviceRelations struct. Arguments: DeviceEvent - On entry, contains the original device event block, on return it contains the newly allocated device event block and a complete list of related device id strings. DeviceRelations - structure that contains a list of related device objects. Return Value: NTSTATUS value. --*/ { PDEVICE_NODE relatedDeviceNode; PDEVICE_OBJECT relatedDeviceObject; ULONG newSize, currentSize; PPNP_DEVICE_EVENT_ENTRY newDeviceEvent; LPWSTR targetDevice, p; ULONG marker; BOOLEAN directDescendant; PAGED_CODE(); if (RelationsList == NULL) { return STATUS_SUCCESS; // nothing to do } targetDevice = (*DeviceEvent)->Data.u.TargetDevice.DeviceIds; // // Calculate the size of the PNP_DEVICE_EVENT_ENTRY block // currentSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + (*DeviceEvent)->Data.TotalSize; newSize = currentSize; newSize -= (ULONG)((wcslen(targetDevice)+1) * sizeof(WCHAR)); marker = 0; while (IopEnumerateRelations( RelationsList, &marker, &relatedDeviceObject, &directDescendant, NULL, FALSE)) { if (!ExcludeIndirectRelations || directDescendant) { relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if (relatedDeviceNode != NULL) { if (relatedDeviceNode->InstancePath.Length != 0) { newSize += relatedDeviceNode->InstancePath.Length + sizeof(WCHAR); } } } } ASSERT(newSize >= currentSize); if (newSize == currentSize) { return STATUS_SUCCESS; } else if (newSize < currentSize) { newSize = currentSize; } newDeviceEvent = (PPNP_DEVICE_EVENT_ENTRY) PiAllocateCriticalMemory( DeleteType, PagedPool, newSize, PNP_DEVICE_EVENT_ENTRY_TAG ); if (newDeviceEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory((PVOID)newDeviceEvent, newSize); // // Copy the old buffer into the new buffer, it's only the new stuff at the // end that changes. // RtlCopyMemory(newDeviceEvent, *DeviceEvent, currentSize); // // Update the size of the PLUGPLAY_EVENT_BLOCK // newDeviceEvent->Data.TotalSize = newSize - FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data); // // Add device instance string for each device relation to the list. // Leave the target device first in the list, and skip it during the // enumeration below. // marker = 0; p = newDeviceEvent->Data.u.TargetDevice.DeviceIds + wcslen(targetDevice) + 1; while (IopEnumerateRelations( RelationsList, &marker, &relatedDeviceObject, &directDescendant, NULL, FALSE)) { if ((relatedDeviceObject != newDeviceEvent->Data.DeviceObject) && (!ExcludeIndirectRelations || directDescendant)) { relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if (relatedDeviceNode != NULL) { if (relatedDeviceNode->InstancePath.Length != 0) { RtlCopyMemory(p, relatedDeviceNode->InstancePath.Buffer, relatedDeviceNode->InstancePath.Length); p += relatedDeviceNode->InstancePath.Length / sizeof(WCHAR) + 1; } } } } *p = UNICODE_NULL; ExFreePool(*DeviceEvent); *DeviceEvent = newDeviceEvent; return STATUS_SUCCESS; } // PiResizeTargetDeviceBlock VOID PiBuildUnsafeRemovalDeviceBlock( IN PPNP_DEVICE_EVENT_ENTRY OriginalDeviceEvent, IN PRELATION_LIST RelationsList, OUT PPNP_DEVICE_EVENT_ENTRY *AllocatedDeviceEvent ) /*++ Routine Description: This routine builds a device event block to send to user mode in case of unsafe removal. Arguments: OriginalDeviceEvent - Contains the original device event block. RelationList - structure that contains a list of related device objects. AllocatedDeviceEvent - Receives the new device event, NULL on error or no entries. Return Value: None. --*/ { PDEVICE_NODE relatedDeviceNode; PDEVICE_OBJECT relatedDeviceObject; ULONG dataSize, eventSize, headerSize; PPNP_DEVICE_EVENT_ENTRY newDeviceEvent; LPWSTR targetDevice, p; ULONG marker; BOOLEAN directDescendant; PAGED_CODE(); // // Preinit // *AllocatedDeviceEvent = NULL; if (RelationsList == NULL) { return; // nothing to do } targetDevice = OriginalDeviceEvent->Data.u.TargetDevice.DeviceIds; // // Calculate the size of the PNP_DEVICE_EVENT_ENTRY block // dataSize = 0; marker = 0; while (IopEnumerateRelations( RelationsList, &marker, &relatedDeviceObject, &directDescendant, NULL, FALSE)) { if (!directDescendant) { continue; } relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if ((relatedDeviceNode == NULL) || PipIsBeingRemovedSafely(relatedDeviceNode)) { continue; } if (relatedDeviceNode->InstancePath.Length != 0) { dataSize += relatedDeviceNode->InstancePath.Length + sizeof(WCHAR); } } if (dataSize == 0) { // // No entries, bail. // return; } // // Add the terminating MultiSz NULL. // dataSize += sizeof(WCHAR); headerSize = FIELD_OFFSET(PNP_DEVICE_EVENT_ENTRY, Data) + FIELD_OFFSET(PLUGPLAY_EVENT_BLOCK, u); eventSize = dataSize + headerSize; // // If we can't get memory, there simply won't be a message sent. // newDeviceEvent = ExAllocatePoolWithTag( PagedPool, eventSize, PNP_DEVICE_EVENT_ENTRY_TAG ); if (newDeviceEvent == NULL) { return; } RtlZeroMemory((PVOID)newDeviceEvent, eventSize); // // Copy the header into the new buffer. // RtlCopyMemory(newDeviceEvent, OriginalDeviceEvent, headerSize); // // Update the size of the PLUGPLAY_EVENT_BLOCK // newDeviceEvent->Data.TotalSize = dataSize + FIELD_OFFSET(PLUGPLAY_EVENT_BLOCK, u); // // Add device instance string for each device relation to the list. // marker = 0; p = newDeviceEvent->Data.u.TargetDevice.DeviceIds; while (IopEnumerateRelations( RelationsList, &marker, &relatedDeviceObject, &directDescendant, NULL, FALSE)) { if (!directDescendant) { continue; } relatedDeviceNode = (PDEVICE_NODE)relatedDeviceObject->DeviceObjectExtension->DeviceNode; if ((relatedDeviceNode == NULL) || PipIsBeingRemovedSafely(relatedDeviceNode)) { continue; } if (relatedDeviceNode->InstancePath.Length != 0) { RtlCopyMemory(p, relatedDeviceNode->InstancePath.Buffer, relatedDeviceNode->InstancePath.Length); p += relatedDeviceNode->InstancePath.Length / sizeof(WCHAR) + 1; } } *p = UNICODE_NULL; *AllocatedDeviceEvent = newDeviceEvent; return; } // PiBuildUnsafeRemovalDeviceBlock VOID PiFinalizeVetoedRemove( IN PPNP_DEVICE_EVENT_ENTRY VetoedDeviceEvent, IN PNP_VETO_TYPE VetoType, IN PUNICODE_STRING VetoName OPTIONAL ) /*++ Routine Description: This routine takes care of updating the event results with the veto information, puts up UI if neccessary, and dumps failure information to the debugger for debugging purposes. Arguments: VetoedDeviceEvent - Data describing the device event failed. VetoType - The veto code best describing why the operation failed. VetoName - A unicode string appropriate to the veto code that describes the vetoer. Return Value: None. --*/ { PDEVICE_OBJECT deviceObject; #if DBG PUNICODE_STRING devNodeName; const char *failureReason; #endif deviceObject = (PDEVICE_OBJECT) VetoedDeviceEvent->Data.DeviceObject; #if DBG devNodeName = &((PDEVICE_NODE) deviceObject->DeviceObjectExtension->DeviceNode)->InstancePath; switch(VetoType) { case PNP_VetoTypeUnknown: failureReason = "for unspecified reason"; break; case PNP_VetoLegacyDevice: failureReason = "due to legacy device"; break; case PNP_VetoPendingClose: // // ADRIAO N.B. 07/10/2000 - I believe this case is vestigal... // ASSERT(0); failureReason = "due to pending close"; break; case PNP_VetoWindowsApp: failureReason = "due to windows application"; break; case PNP_VetoWindowsService: failureReason = "due to service"; break; case PNP_VetoOutstandingOpen: failureReason = "due to outstanding handles on device"; break; case PNP_VetoDevice: failureReason = "by device"; break; case PNP_VetoDriver: failureReason = "by driver"; break; case PNP_VetoIllegalDeviceRequest: failureReason = "as the request was invalid for the device"; break; case PNP_VetoInsufficientPower: failureReason = "because there would be insufficient system power to continue"; break; case PNP_VetoNonDisableable: failureReason = "due to non-disableable device"; break; case PNP_VetoLegacyDriver: failureReason = "due to legacy driver"; break; case PNP_VetoInsufficientRights: failureReason = "insufficient permissions"; break; default: ASSERT(0); failureReason = "due to uncoded reason"; break; } if (VetoName != NULL) { IopDbgPrint((IOP_IOEVENT_WARNING_LEVEL, "PiFinalizeVetoedRemove: Removal of %wZ vetoed %s %wZ.\n", devNodeName, failureReason, VetoName )); } else { IopDbgPrint((IOP_IOEVENT_WARNING_LEVEL, "PiFinalizeVetoedRemove: Removal of %wZ vetoed %s.\n", devNodeName, failureReason )); } #endif // // Update the vetoType field if the caller is interested. // if (VetoedDeviceEvent->VetoType != NULL) { *VetoedDeviceEvent->VetoType = VetoType; } // // The VetoName field tells us whether UI should be displayed (if NULL, // kernel mode UI is implicitely requested.) // if (VetoedDeviceEvent->VetoName != NULL) { if (VetoName != NULL) { RtlCopyUnicodeString(VetoedDeviceEvent->VetoName, VetoName); } } else { // // If there is not a VetoName passed in then call user mode to display the // eject veto notification to the user // PiNotifyUserModeRemoveVetoed( VetoedDeviceEvent, deviceObject, VetoType, VetoName ); } } BOOLEAN PiCompareGuid( CONST GUID *Guid1, CONST GUID *Guid2 ) /*++ Routine Description: This routine compares two guids. Arguments: Guid1 - First guid to compare Guid2 - Second guid to compare Return Value: Returns TRUE if the guids are equal and FALSE if they're different. --*/ { PAGED_CODE(); if (RtlCompareMemory((PVOID)Guid1, (PVOID)Guid2, sizeof(GUID)) == sizeof(GUID)) { return TRUE; } return FALSE; } // PiCompareGuid PVOID PiAllocateCriticalMemory( IN PLUGPLAY_DEVICE_DELETE_TYPE DeleteType, IN POOL_TYPE PoolType, IN SIZE_T Size, IN ULONG Tag ) /*++ Routine Description: This function allocates memory and never fails if the DeleteType isn't QueryRemoveDevice or EjectDevice. This function will disappear in the next version of the PnP engine as we will instead requeue failed operations (which will also result in a second attempt to allocate the memory) or preallocate the required memory when bringing new devnode's into the world. Arguments: DeleteType - Operation (EjectDevice, SurpriseRemoveDevice, ...) PoolType - PagedPool, NonPagedPool Size - Size Tag - Allocation tag Return Value: Allocation, NULL due to insufficient resources. --*/ { PVOID memory; LARGE_INTEGER timeOut; PAGED_CODE(); // // Retries only have a hope of succeeding if we are at PASSIVE_LEVEL // ASSERT(KeGetCurrentIrql() != DISPATCH_LEVEL); while(1) { memory = ExAllocatePoolWithTag(PoolType, Size, Tag); if (memory || (DeleteType == QueryRemoveDevice) || (DeleteType == EjectDevice)) { // // Either we got memory or the op was failable. Get out of here. // break; } // // We're stuck until more memory comes along. Let some other // threads run before we get another shot... // timeOut.QuadPart = Int32x32To64( 1, -10000 ); KeDelayExecutionThread(KernelMode, FALSE, &timeOut); } return memory; } struct { CONST GUID *Guid; PCHAR Name; } EventGuidTable[] = { { &GUID_HWPROFILE_QUERY_CHANGE, "GUID_HWPROFILE_QUERY_CHANGE" }, { &GUID_HWPROFILE_CHANGE_CANCELLED, "GUID_HWPROFILE_CHANGE_CANCELLED" }, { &GUID_HWPROFILE_CHANGE_COMPLETE, "GUID_HWPROFILE_CHANGE_COMPLETE" }, { &GUID_DEVICE_INTERFACE_ARRIVAL, "GUID_DEVICE_INTERFACE_ARRIVAL" }, { &GUID_DEVICE_INTERFACE_REMOVAL, "GUID_DEVICE_INTERFACE_REMOVAL" }, { &GUID_TARGET_DEVICE_QUERY_REMOVE, "GUID_TARGET_DEVICE_QUERY_REMOVE" }, { &GUID_TARGET_DEVICE_REMOVE_CANCELLED, "GUID_TARGET_DEVICE_REMOVE_CANCELLED" }, { &GUID_TARGET_DEVICE_REMOVE_COMPLETE, "GUID_TARGET_DEVICE_REMOVE_COMPLETE" }, { &GUID_PNP_CUSTOM_NOTIFICATION, "GUID_PNP_CUSTOM_NOTIFICATION" }, { &GUID_DEVICE_ARRIVAL, "GUID_DEVICE_ARRIVAL" }, { &GUID_DEVICE_ENUMERATED, "GUID_DEVICE_ENUMERATED" }, { &GUID_DEVICE_ENUMERATE_REQUEST, "GUID_DEVICE_ENUMERATE_REQUEST" }, { &GUID_DEVICE_START_REQUEST, "GUID_DEVICE_START_REQUEST" }, { &GUID_DEVICE_REMOVE_PENDING, "GUID_DEVICE_REMOVE_PENDING" }, { &GUID_DEVICE_QUERY_AND_REMOVE, "GUID_DEVICE_QUERY_AND_REMOVE" }, { &GUID_DEVICE_EJECT, "GUID_DEVICE_EJECT" }, { &GUID_DEVICE_NOOP, "GUID_DEVICE_NOOP" }, { &GUID_DEVICE_SURPRISE_REMOVAL, "GUID_DEVICE_SURPRISE_REMOVAL" }, { &GUID_DEVICE_SAFE_REMOVAL, "GUID_DEVICE_SAFE_REMOVAL" }, { &GUID_DEVICE_EJECT_VETOED, "GUID_DEVICE_EJECT_VETOED" }, { &GUID_DEVICE_REMOVAL_VETOED, "GUID_DEVICE_REMOVAL_VETOED" }, }; #define EVENT_GUID_TABLE_SIZE (sizeof(EventGuidTable) / sizeof(EventGuidTable[0])) VOID LookupGuid( IN CONST GUID *Guid, IN OUT PCHAR String, IN ULONG StringLength ) { int i; PAGED_CODE(); for (i = 0; i < EVENT_GUID_TABLE_SIZE; i++) { if (PiCompareGuid(Guid, EventGuidTable[i].Guid)) { strncpy(String, EventGuidTable[i].Name, StringLength - 1); String[StringLength - 1] = '\0'; return; } } StringCchPrintfA( String, StringLength, "%08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X", Guid->Data1, Guid->Data2, Guid->Data3, Guid->Data4[0], Guid->Data4[1], Guid->Data4[2], Guid->Data4[3], Guid->Data4[4], Guid->Data4[5], Guid->Data4[6], Guid->Data4[7] ); } VOID DumpMultiSz( IN PWCHAR MultiSz ) { PWCHAR p = MultiSz; ULONG length; PAGED_CODE(); while (*p) { length = (ULONG)wcslen(p); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " %S\n", p)); p += length + 1; } } VOID DumpPnpEvent( IN PPLUGPLAY_EVENT_BLOCK EventBlock ) { CHAR guidString[256]; PAGED_CODE(); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, "PlugPlay Event Block @ 0x%p\n", EventBlock)); LookupGuid(&EventBlock->EventGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " EventGuid = %s\n", guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceObject = 0x%p\n", EventBlock->DeviceObject)); switch (EventBlock->EventCategory) { case HardwareProfileChangeEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " HardwareProfileChangeEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); break; case TargetDeviceChangeEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " TargetDeviceChangeEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceIds:\n")); DumpMultiSz( EventBlock->u.TargetDevice.DeviceIds ); break; case DeviceClassChangeEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceClassChangeEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); LookupGuid(&EventBlock->u.DeviceClass.ClassGuid, guidString, sizeof(guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " ClassGuid = %s\n", guidString)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " SymbolicLinkName = %S\n", EventBlock->u.DeviceClass.SymbolicLinkName)); break; case CustomDeviceEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " CustomDeviceEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " NotificationStructure = 0x%p\n DeviceIds:\n", EventBlock->u.CustomNotification.NotificationStructure)); DumpMultiSz( EventBlock->u.CustomNotification.DeviceIds ); break; case DeviceInstallEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceInstallEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceId = %S\n", EventBlock->u.InstallDevice.DeviceId)); break; case DeviceArrivalEvent: IopDbgPrint((IOP_IOEVENT_INFO_LEVEL, " DeviceArrivalEvent, Result = 0x%p, Flags = 0x%08X, TotalSize = %d\n", EventBlock->Result, EventBlock->Flags, EventBlock->TotalSize)); break; } } LOGICAL PiCollectOpenHandles( IN PDEVICE_OBJECT *DeviceObjectArray, IN ULONG ArrayCount, IN LOGICAL KnownHandleFailure, IN OUT PUNICODE_STRING VetoString ) /*++ Routine Description: This helper routine finds any handles opened against the passed in array of device objects to either the veto string, the debugger console, or neither. Arguments: DeviceObjectArray - Array of Physical Device Objects. ArrayCount - Number of device objects in the passed in array KnownHandleFailure - TRUE if the removal was vetoed due to open handles, FALSE if not. VetoString - String to populate with veto information if told. This data is not currently "sanitized" enough to be user-readable. Return Value: TRUE if veto information populated, FALSE otherwise. --*/ { ULONG i; LOGICAL collectHandles, dumpHandles; ENUM_HANDLES_CONTEXT enumContext; // // If we have enabled the dumping flag, or the user ran oh.exe, spit all // handles on a veto to the debugger. // dumpHandles = (PiDumpVetoedHandles || ((NtGlobalFlag & FLG_MAINTAIN_OBJECT_TYPELIST) != 0)); if (dumpHandles) { DbgPrint("Beginning handle dump:\n"); if (!KnownHandleFailure) { DbgPrint(" (Failed Query-Remove - *Might* by due to leaked handles)\n"); } } collectHandles = PiCollectVetoedHandles; if (!(collectHandles || dumpHandles)) { return FALSE; } VetoString->Length = 0; ASSERT(VetoString->MaximumLength >= sizeof(WCHAR)); *VetoString->Buffer = UNICODE_NULL; enumContext.DumpHandles = dumpHandles; enumContext.CollectHandles = collectHandles; enumContext.VetoString = VetoString; enumContext.HandleCount = 0; for(i=0; iDumpHandles) { // // Display the handle. // DbgPrint( " DeviceObject:%p ProcessID:%dT FileObject:%p Handle:%dT\n", DeviceObject, Process->UniqueProcessId, FileObject, HandleId ); } if (enumContext->CollectHandles) { result = StringCbPrintfW( localBuf, sizeof(localBuf), L"%dT.0x%08x ", (ULONG)(ULONG_PTR)Process->UniqueProcessId, (ULONG)(ULONG_PTR)HandleId ); if (SUCCEEDED(result)) { result = StringCbCatExW( enumContext->VetoString->Buffer, enumContext->VetoString->MaximumLength, localBuf, &endString, NULL, STRSAFE_NO_TRUNCATION ); if (SUCCEEDED(result)) { enumContext->VetoString->Length = (USHORT) (endString - enumContext->VetoString->Buffer)*sizeof(WCHAR); } } } enumContext->HandleCount++; return FALSE; }