/*++ Copyright (c) 1998-2000 Microsoft Corporation Module Name: PpProfile.c Abstract: Kernel-mode Plug and Play Manager Docking and Hardware Profile Support Routines. Author: Adrian J. Oney (AdriaO) June 1998 Kenneth D. Ray (kenray) June 1998 Revision History: --*/ #include "pnpmgrp.h" // // ISSUE-2000/07/24-AdriaO - Header mess // We should not be including private headers from CM. // #undef ExAllocatePool #undef ExAllocatePoolWithQuota #include "..\config\cmp.h" #include "piprofile.h" #pragma hdrstop #ifdef ALLOC_PRAGMA #pragma alloc_text(INIT, PpProfileInit) #pragma alloc_text(PAGE, PpProfileBeginHardwareProfileTransition) #pragma alloc_text(PAGE, PpProfileIncludeInHardwareProfileTransition) #pragma alloc_text(PAGE, PpProfileQueryHardwareProfileChange) #pragma alloc_text(PAGE, PpProfileCommitTransitioningDock) #pragma alloc_text(PAGE, PpProfileCancelTransitioningDock) #pragma alloc_text(PAGE, PpProfileCancelHardwareProfileTransition) #pragma alloc_text(PAGE, PpProfileMarkAllTransitioningDocksEjected) #pragma alloc_text(PAGE, PiProfileSendHardwareProfileCommit) #pragma alloc_text(PAGE, PiProfileSendHardwareProfileCancel) #pragma alloc_text(PAGE, PiProfileConvertFakeDockToRealDock) #pragma alloc_text(PAGE, PiProfileRetrievePreferredCallback) #pragma alloc_text(PAGE, PpProfileRetrievePreferredDockToEject) #pragma alloc_text(PAGE, PiProfileUpdateDeviceTree) #pragma alloc_text(PAGE, PiProfileUpdateDeviceTreeWorker) #pragma alloc_text(PAGE, PiProfileUpdateDeviceTreeCallback) #endif // // List of current dock devices, and the number of dockdevices. // Must hold PiProfileDeviceListLock to change these values. // LIST_ENTRY PiProfileDeviceListHead; ULONG PiProfileDeviceCount; FAST_MUTEX PiProfileDeviceListLock; KSEMAPHORE PiProfileChangeSemaphore; BOOLEAN PiProfileChangeCancelRequired; LONG PiProfileDevicesInTransition; VOID PpProfileInit( VOID ) /*++ Routine Description: This routine initializes docking support for Win2K. Arguments: None. Return Value: Nope. --*/ { // // Initialize the list of dock devices, and its lock. // InitializeListHead(&PiProfileDeviceListHead); ExInitializeFastMutex(&PiProfileDeviceListLock); PiProfileDeviceCount = 0; KeInitializeSemaphore(&PiProfileChangeSemaphore, 1, 1); } VOID PpProfileBeginHardwareProfileTransition( IN BOOLEAN SubsumeExistingDeparture ) /*++ Routine Description: This routine must be called before any dock devnodes can be marked for transition (ie arriving or departing). After calling this function, PpProfileIncludeInHardwareProfileTransition should be called for each dock that is appearing or disappearing. Functionally, this code acquires the profile change semaphore. Future changes in the life of the added dock devnodes cause it to be released. Arguments: SubsumeExistingDeparture - Set if we are ejecting the parent of a device that is still in the process of ejecting... Return Value: None. --*/ { NTSTATUS status; if (SubsumeExistingDeparture) { // // We will already have queried in this case. Also, enumeration is // locked right now, so the appropriate devices found cannot disappear. // Assert everything is consistant. // ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); ASSERT(PiProfileDevicesInTransition != 0); return; } // // Take the profile change semaphore. We do this whenever a dock is // in our list, even if no query is going to occur. // status = KeWaitForSingleObject( &PiProfileChangeSemaphore, Executive, KernelMode, FALSE, NULL ); ASSERT(status == STATUS_SUCCESS); } VOID PpProfileIncludeInHardwareProfileTransition( IN PDEVICE_NODE DeviceNode, IN PROFILE_STATUS ChangeInPresence ) /*++ Routine Description: This routine is called to mark a dock as "in transition", ie it is either disappearing or appearing, the results of which determine our final hardware profile state. After all the docks that are transitioning have been passed into this function, PiProfileQueryHardwareProfileChange is called. Arguments: DeviceNode - The dock devnode that is appearing or disappearing ChangeInPresence - Either DOCK_DEPARTING or DOCK_ARRIVING Return Value: Nope. --*/ { PWCHAR deviceSerialNumber; PDEVICE_OBJECT deviceObject; NTSTATUS status; // // Verify we are under semaphore, we aren't marking the dock twice, and // our parameters are sensable. // ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); ASSERT(DeviceNode->DockInfo.DockStatus == DOCK_QUIESCENT); ASSERT((ChangeInPresence == DOCK_DEPARTING)|| (ChangeInPresence == DOCK_ARRIVING)); if (ChangeInPresence == DOCK_ARRIVING) { // // First, ensure this dock is a member of the dock list. // // ADRIAO N.B. 07/09/2000 - // We should move this into IopProcessNewDeviceNode, or perhaps // PipStartPhaseN. // if (IsListEmpty(&DeviceNode->DockInfo.ListEntry)) { // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); // // Add this element to the head of the list // InsertHeadList(&PiProfileDeviceListHead, &DeviceNode->DockInfo.ListEntry); PiProfileDeviceCount++; // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); } // // Retrieve the Serial Number from this dock device. We do this just // to test the BIOS today. Later we will be acquiring the information // to determine the profile we are *about* to enter. // deviceObject = DeviceNode->PhysicalDeviceObject; status = IopQueryDeviceSerialNumber( deviceObject, &deviceSerialNumber ); if (NT_SUCCESS(status) && (deviceSerialNumber != NULL)) { ExFreePool(deviceSerialNumber); } } else { // // DOCK_DEPARTING case, we must be a member of the dock list... // ASSERT(!IsListEmpty(&DeviceNode->DockInfo.ListEntry)); } InterlockedIncrement(&PiProfileDevicesInTransition); DeviceNode->DockInfo.DockStatus = ChangeInPresence; } NTSTATUS PpProfileQueryHardwareProfileChange( IN BOOLEAN SubsumingExistingDeparture, IN PROFILE_NOTIFICATION_TIME InPnpEvent, OUT PPNP_VETO_TYPE VetoType, OUT PUNICODE_STRING VetoName OPTIONAL ) /*++ Routine Description: This function queries drivers to see if it is OK to exit the current hardware profile and enter next one (as determined by which docks have been marked). One of two functions should be used subsequently to this call: PpProfileCommitTransitioningDock (call when a dock has been successfully started or has disappeared) PpProfileCancelHardwareProfileTransition (call to abort a transition, say if a dock failed to start or a query returned failure for eject) Arguments: InPnpEvent - This argument indicates whether an operation is being done within the context of another PnpEvent or not. If not, we will queue such an event and block on it. If so, we cannot queue&block (we'd deadlock), so we do the query manually. VetoType - If this function returns false, this parameter will describe who failed the query profile change. The below optional parameter will contain the name of said vetoer. VetoName - This optional parameter will get the name of the vetoer (ie devinst, service name, application name, etc). If VetoName is supplied, the caller must free the buffer returned. Return Value: NTSTATUS. --*/ { NTSTATUS status; BOOLEAN arrivingDockFound; PLIST_ENTRY listEntry; PDEVICE_NODE devNode; ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); // // Acquire the lock on the list of dock devices and determine whether any // dock devnodes are arriving. // ExAcquireFastMutex(&PiProfileDeviceListLock); ASSERT(PiProfileDevicesInTransition); arrivingDockFound = FALSE; for (listEntry = PiProfileDeviceListHead.Flink; listEntry != &(PiProfileDeviceListHead); listEntry = listEntry->Flink ) { devNode = CONTAINING_RECORD(listEntry, DEVICE_NODE, DockInfo.ListEntry); ASSERT((devNode->DockInfo.DockStatus != DOCK_NOTDOCKDEVICE)&& (devNode->DockInfo.DockStatus != DOCK_EJECTIRP_COMPLETED)); if (devNode->DockInfo.DockStatus == DOCK_ARRIVING) { arrivingDockFound = TRUE; } } // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); if (SubsumingExistingDeparture) { ASSERT(PiProfileChangeCancelRequired); // // We're nesting. Work off the last query, and don't requery. // return STATUS_SUCCESS; } if (arrivingDockFound) { // // We currently don't actually query for hardware profile change on a // dock event as the user may have the lid closed. If we ever find a // piece of hardware that needs to be updated *prior* to actually // switching over, we will have to remove this bit of code. // PiProfileChangeCancelRequired = FALSE; return STATUS_SUCCESS; } IopDbgPrint((IOP_TRACE_LEVEL, "NTOSKRNL: Sending HW profile change [query]\n")); status = IopRequestHwProfileChangeNotification( (LPGUID) &GUID_HWPROFILE_QUERY_CHANGE, InPnpEvent, VetoType, VetoName ); if (NT_SUCCESS(status)) { PiProfileChangeCancelRequired = TRUE; } else { PiProfileChangeCancelRequired = FALSE; } return status; } VOID PpProfileCommitTransitioningDock( IN PDEVICE_NODE DeviceNode, IN PROFILE_STATUS ChangeInPresence ) /*++ Routine Description: This routine finalized the state the specified device in the list of current dock devices and requests a Hardware Profile change. Arguments: DeviceNode - The dock devnode that has finished being started or removed. ChangeInPresence - Either DOCK_DEPARTING or DOCK_ARRIVING Return Value: None. --*/ { NTSTATUS status; PDEVICE_OBJECT deviceObject; PWCHAR deviceSerialNumber; BOOLEAN profileChanged; LONG remainingDockCount; // // If we are commiting a dock, the transition list should not be empty. // all dock devices present, the list should not be empty. // ASSERT(!IsListEmpty(&DeviceNode->DockInfo.ListEntry)); ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); if (ChangeInPresence == DOCK_DEPARTING) { ASSERT((DeviceNode->DockInfo.DockStatus == DOCK_DEPARTING) || (DeviceNode->DockInfo.DockStatus == DOCK_EJECTIRP_COMPLETED)); // // Free up the serial number // if (DeviceNode->DockInfo.SerialNumber != NULL) { ExFreePool(DeviceNode->DockInfo.SerialNumber); DeviceNode->DockInfo.SerialNumber = NULL; } // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); // // Remove the current devnode from the list of docks // RemoveEntryList(&DeviceNode->DockInfo.ListEntry); InitializeListHead(&DeviceNode->DockInfo.ListEntry); PiProfileDeviceCount--; // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); } else { ASSERT(DeviceNode->DockInfo.DockStatus == DOCK_ARRIVING); // // We only add one dock at a time. So this should have been the last! // ASSERT(PiProfileDevicesInTransition == 1); // // Retrieve the Serial Number from this dock device if we don't already // have it. // if (DeviceNode->DockInfo.SerialNumber == NULL) { deviceObject = DeviceNode->PhysicalDeviceObject; status = IopQueryDeviceSerialNumber(deviceObject, &deviceSerialNumber); DeviceNode->DockInfo.SerialNumber = deviceSerialNumber; } } DeviceNode->DockInfo.DockStatus = DOCK_QUIESCENT; remainingDockCount = InterlockedDecrement(&PiProfileDevicesInTransition); ASSERT(remainingDockCount >= 0); if (remainingDockCount) { return; } profileChanged = FALSE; if ((ChangeInPresence == DOCK_ARRIVING) && (DeviceNode->DockInfo.SerialNumber == NULL)) { // // Couldn't get Serial Number for this dock device, or serial number // was NULL. We can make this check here as only one dock at a time // can currently arrive. // status = STATUS_UNSUCCESSFUL; goto BroadcastAndLeave; } // // Update the current Hardware Profile now that the transition list has // been emptied. This routine does two things for us: // 1) It determines whether the profile actually changed and updates // the global flag IopProfileChangeOccured appropriately. // 2) If the profile changed, this routine updates the registry. // status = PiProfileUpdateHardwareProfile(&profileChanged); if (!NT_SUCCESS(status)) { IopDbgPrint((IOP_TRACE_LEVEL, "PiProfileUpdateHardwareProfile failed with status == %lx\n", status)); } BroadcastAndLeave: // // Clean up // if (NT_SUCCESS(status) && profileChanged) { PiProfileSendHardwareProfileCommit(); PiProfileUpdateDeviceTree(); } else if (PiProfileChangeCancelRequired) { PiProfileSendHardwareProfileCancel(); } KeReleaseSemaphore( &PiProfileChangeSemaphore, IO_NO_INCREMENT, 1, FALSE ); return; } VOID PpProfileCancelTransitioningDock( IN PDEVICE_NODE DeviceNode, IN PROFILE_STATUS ChangeInPresence ) /*++ Routine Description: This routine is called when a dock that was marked to disappear didn't (ie, after the eject, the dock device still enumerated). We remove it from the transition list and complete/cancel the HW profile change as appropriate. See PpProfileMarkAllTransitioningDocksEjected. Arguments: DeviceNode - The dock devnode that either didn't start or didn't disappear. ChangeInPresence - Either DOCK_DEPARTING or DOCK_ARRIVING N.B. - Currently only DOCK_DEPARTING is supported. Return Value: None. --*/ { NTSTATUS status; BOOLEAN profileChanged; LONG remainingDockCount; ASSERT(ChangeInPresence == DOCK_DEPARTING); // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); // // Since we are about to remove this dock device from the list of // all dock devices present, the list should not be empty. // ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); ASSERT(DeviceNode->DockInfo.DockStatus == DOCK_EJECTIRP_COMPLETED); ASSERT(!IsListEmpty(&DeviceNode->DockInfo.ListEntry)); DeviceNode->DockInfo.DockStatus = DOCK_QUIESCENT; remainingDockCount = InterlockedDecrement(&PiProfileDevicesInTransition); ASSERT(remainingDockCount >= 0); // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); if (remainingDockCount) { return; } // // Update the current Hardware Profile after removing this device. // status = PiProfileUpdateHardwareProfile(&profileChanged); if (!NT_SUCCESS(status)) { // // So we're there physically, but not mentally? Too bad, where broadcasting // change either way. // IopDbgPrint((IOP_TRACE_LEVEL, "PiProfileUpdateHardwareProfile failed with status == %lx\n", status)); ASSERT(NT_SUCCESS(status)); } if (NT_SUCCESS(status) && profileChanged) { PiProfileSendHardwareProfileCommit(); PiProfileUpdateDeviceTree(); } else { ASSERT(PiProfileChangeCancelRequired); PiProfileSendHardwareProfileCancel(); } KeReleaseSemaphore( &PiProfileChangeSemaphore, IO_NO_INCREMENT, 1, FALSE ); return; } VOID PpProfileCancelHardwareProfileTransition( VOID ) /*++ Routine Description: This routine unmarks any marked devnodes (ie, sets them to no change, appearing or disappearing), and sends the CancelQueryProfileChange as appropriate. Once called, other profile changes can occur. Arguments: None. Return Value: Nodda. --*/ { PLIST_ENTRY listEntry; PDEVICE_NODE devNode; ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); for (listEntry = PiProfileDeviceListHead.Flink; listEntry != &(PiProfileDeviceListHead); listEntry = listEntry->Flink ) { devNode = CONTAINING_RECORD(listEntry, DEVICE_NODE, DockInfo.ListEntry); ASSERT((devNode->DockInfo.DockStatus != DOCK_NOTDOCKDEVICE)&& (devNode->DockInfo.DockStatus != DOCK_EJECTIRP_COMPLETED)); if (devNode->DockInfo.DockStatus != DOCK_QUIESCENT) { InterlockedDecrement(&PiProfileDevicesInTransition); devNode->DockInfo.DockStatus = DOCK_QUIESCENT; } } ASSERT(!PiProfileDevicesInTransition); // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); if (PiProfileChangeCancelRequired) { PiProfileSendHardwareProfileCancel(); } KeReleaseSemaphore( &PiProfileChangeSemaphore, IO_NO_INCREMENT, 1, FALSE ); } VOID PpProfileMarkAllTransitioningDocksEjected( VOID ) /*++ Routine Description: This routine moves any departing devnodes to the ejected state. If any subsequent enumeration lists the device as present, we know the eject failed and we appropriately cancel that piece of the profile change. PpProfileCancelTransitioningDock can only be called after this function is called. Arguments: None. Return Value: Nodda. --*/ { PLIST_ENTRY listEntry; PDEVICE_NODE devNode; // // The semaphore might not be signalled if the dock was resurrected before // the eject completed. This can happen in warm undock scenarios where the // machine is resumed inside the dock instead of being detached. // //ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); for (listEntry = PiProfileDeviceListHead.Flink; listEntry != &(PiProfileDeviceListHead); listEntry = listEntry->Flink ) { devNode = CONTAINING_RECORD(listEntry, DEVICE_NODE, DockInfo.ListEntry); ASSERT((devNode->DockInfo.DockStatus == DOCK_QUIESCENT)|| (devNode->DockInfo.DockStatus == DOCK_DEPARTING)); if (devNode->DockInfo.DockStatus != DOCK_QUIESCENT) { devNode->DockInfo.DockStatus = DOCK_EJECTIRP_COMPLETED; } } // // Release the lock on the list of dock devices // ExReleaseFastMutex(&PiProfileDeviceListLock); } VOID PiProfileSendHardwareProfileCommit( VOID ) /*++ Routine Description: This routine (internal to ppdock.c) simply sends the change complete message. We do not wait for this, as it is asynchronous... Arguments: None. Return Value: Nodda. --*/ { ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); IopDbgPrint((IOP_TRACE_LEVEL, "NTOSKRNL: Sending HW profile change [commit]\n")); IopRequestHwProfileChangeNotification( (LPGUID) &GUID_HWPROFILE_CHANGE_COMPLETE, PROFILE_PERHAPS_IN_PNPEVENT, NULL, NULL ); } VOID PiProfileSendHardwareProfileCancel( VOID ) /*++ Routine Description: This routine (internal to ppdock.c) simply sends the cancel. Arguments: None. Return Value: Nodda. --*/ { ASSERT_SEMA_NOT_SIGNALLED(&PiProfileChangeSemaphore); IopDbgPrint((IOP_TRACE_LEVEL, "NTOSKRNL: Sending HW profile change [cancel]\n")); IopRequestHwProfileChangeNotification( (LPGUID) &GUID_HWPROFILE_CHANGE_CANCELLED, PROFILE_PERHAPS_IN_PNPEVENT, NULL, NULL ); } NTSTATUS PiProfileUpdateHardwareProfile( OUT BOOLEAN *ProfileChanged ) /*++ Routine Description: This routine scans the list of current dock devices, builds a list of serial numbers from those devices, and calls for the Hardware Profile to be changed, based on that list. Arguments: ProfileChanged - Supplies a variable to receive TRUE if the current hardware profile changes as a result of calling this routine. Return Value: NTSTATUS code. --*/ { NTSTATUS status = STATUS_SUCCESS; PLIST_ENTRY listEntry; PDEVICE_NODE devNode; PWCHAR *profileSerialNumbers, *p; HANDLE hProfileKey=NULL; ULONG len, numProfiles; HANDLE hCurrent, hIDConfigDB; UNICODE_STRING unicodeName; // // Acquire the lock on the list of dock devices // ExAcquireFastMutex(&PiProfileDeviceListLock); // // Update the flag for Ejectable Docks (flag is the count of docks) // PiWstrToUnicodeString(&unicodeName, CM_HARDWARE_PROFILE_STR_DATABASE); if(NT_SUCCESS(IopOpenRegistryKey(&hIDConfigDB, NULL, &unicodeName, KEY_READ, FALSE) )) { PiWstrToUnicodeString(&unicodeName, CM_HARDWARE_PROFILE_STR_CURRENT_DOCK_INFO); if(NT_SUCCESS(IopOpenRegistryKey(&hCurrent, hIDConfigDB, &unicodeName, KEY_READ | KEY_WRITE, FALSE) )) { PiWstrToUnicodeString(&unicodeName, REGSTR_VAL_EJECTABLE_DOCKS); ZwSetValueKey(hCurrent, &unicodeName, 0, REG_DWORD, &PiProfileDeviceCount, sizeof(PiProfileDeviceCount)); ZwClose(hCurrent); } ZwClose(hIDConfigDB); } if (PiProfileDeviceCount == 0) { // // if there are no dock devices, the list should // contain a single null entry, in addition to the null // termination. // numProfiles = 1; ASSERT(IsListEmpty(&PiProfileDeviceListHead)); } else { numProfiles = PiProfileDeviceCount; ASSERT(!IsListEmpty(&PiProfileDeviceListHead)); } // // Allocate space for a null-terminated list of SerialNumber lists. // len = (numProfiles+1)*sizeof(PWCHAR); profileSerialNumbers = ExAllocatePool(NonPagedPool, len); if (profileSerialNumbers) { p = profileSerialNumbers; // // Create the list of Serial Numbers // for (listEntry = PiProfileDeviceListHead.Flink; listEntry != &(PiProfileDeviceListHead); listEntry = listEntry->Flink ) { devNode = CONTAINING_RECORD(listEntry, DEVICE_NODE, DockInfo.ListEntry); ASSERT(devNode->DockInfo.DockStatus == DOCK_QUIESCENT); if (devNode->DockInfo.SerialNumber) { *p = devNode->DockInfo.SerialNumber; p++; } } ExReleaseFastMutex(&PiProfileDeviceListLock); if (p == profileSerialNumbers) { // // Set a single list entry to NULL if we look to be in an "undocked" // profile // *p = NULL; p++; } // // Null-terminate the list // *p = NULL; numProfiles = (ULONG)(p - profileSerialNumbers); // // Change the current Hardware Profile based on the new Dock State // and perform notification that the Hardware Profile has changed // status = IopExecuteHardwareProfileChange(HardwareProfileBusTypeACPI, profileSerialNumbers, numProfiles, &hProfileKey, ProfileChanged); if (hProfileKey) { ZwClose(hProfileKey); } ExFreePool (profileSerialNumbers); } else { ExReleaseFastMutex(&PiProfileDeviceListLock); status = STATUS_INSUFFICIENT_RESOURCES; } return status; } PDEVICE_OBJECT PpProfileRetrievePreferredDockToEject( VOID ) /*++ Routine Description: This routine is called to retrieve the dock that should be ejected via start menu UI. Arguments: None. Return Value: Dock device object if one exists. ++*/ { BEST_DOCK_TO_EJECT bestDock; // // Search for the Dock Nodes // bestDock.PhysicalDeviceObject = NULL; PipForAllDeviceNodes(PiProfileRetrievePreferredCallback, (PVOID)&bestDock); return bestDock.PhysicalDeviceObject; } PDEVICE_NODE PiProfileConvertFakeDockToRealDock( IN PDEVICE_NODE FakeDockDevnode ) /*++ Routine Description: Given a docking Physical Device Object for a fake dock, walk its ejection relations to find out the corresponding real dock node. Arguments: FakeDockDevnode - Fake Dock node Returns Real Dock (PDO referenced once), NULL if none. --*/ { ULONG i; NTSTATUS status; PDEVICE_OBJECT devobj; PDEVICE_NODE devnode, realDock; PDEVICE_RELATIONS ejectRelations = NULL; IO_STACK_LOCATION irpSp; // // Obtain the list of ejection relations. // RtlZeroMemory(&irpSp, sizeof(IO_STACK_LOCATION)); irpSp.MajorFunction = IRP_MJ_PNP; irpSp.MinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS; irpSp.Parameters.QueryDeviceRelations.Type = EjectionRelations; status = IopSynchronousCall( FakeDockDevnode->PhysicalDeviceObject, &irpSp, (PULONG_PTR) &ejectRelations ); if ((!NT_SUCCESS(status)) || (ejectRelations == NULL)) { return NULL; } // // Walk the eject relations looking for the depth. // realDock = NULL; for(i = 0; i < ejectRelations->Count; i++) { devobj = ejectRelations->Objects[i]; // // The last ejection relation is the one that points to the // underlying physically enumerated device. // if (i == ejectRelations->Count-1) { devnode = (PDEVICE_NODE) devobj->DeviceObjectExtension->DeviceNode; // // The devnode might be NULL if: // 1) A driver make a mistake // 2) We got back an ejection relation on a newly created PDO // that hasn't made it's way back up to the OS (we don't // raise the tree lock to BlockReads while an enumeration // IRP is outstanding...) // if (devnode) { realDock = devnode; ObReferenceObject(devobj); } } ObDereferenceObject(devobj); } ExFreePool(ejectRelations); return realDock; } NTSTATUS PiProfileRetrievePreferredCallback( IN PDEVICE_NODE DeviceNode, IN PVOID Context ) /*++ Routine Description: Scan the list of device nodes for docks, and keep the one with the most attractive depth (ie, the one eject PC should select.) Arguments: DeviceNode - Possible docking station DevNode. Context - Pointer to the BEST_DOCK_TO_EJECT structure to fill in. The PhysicalDeviceObject pointer in this structure should be preinited to NULL. The located docking station PDO will be referenced. Returns: NTSTATUS (Unsuccessful status's stop the enumeration of devnodes) --*/ { RTL_QUERY_REGISTRY_TABLE queryTable[2]; PBEST_DOCK_TO_EJECT pBestDock; PDEVICE_NODE realDock, curDock; NTSTATUS status; ULONG dockDepth; HANDLE hDeviceKey; // // Cast the context appropriately. // pBestDock = (PBEST_DOCK_TO_EJECT) Context; // // If it's not a dock device, we will ignore it... // if (!IopDeviceNodeFlagsToCapabilities(DeviceNode)->DockDevice) { // // Continue enumerating. // return STATUS_SUCCESS; } // // First get the corresponding real dock that goes with the fake dock // created by ACPI. // realDock = PiProfileConvertFakeDockToRealDock(DeviceNode); // // Search for overrides. Examine the real dock first, then the fake // curDock = realDock ? realDock : DeviceNode; while(1) { // // Examine the devnode for a specified ejection priority. // status = IoOpenDeviceRegistryKey( curDock->PhysicalDeviceObject, PLUGPLAY_REGKEY_DEVICE, KEY_READ, &hDeviceKey ); if (NT_SUCCESS(status)) { RtlZeroMemory(queryTable, sizeof(queryTable)); dockDepth = 0; queryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED; queryTable[0].Name = (PWSTR) REGSTR_VAL_EJECT_PRIORITY; queryTable[0].EntryContext = &dockDepth; queryTable[0].DefaultType = REG_NONE; queryTable[0].DefaultData = NULL; queryTable[0].DefaultLength = 0; status = RtlQueryRegistryValues( RTL_REGISTRY_HANDLE | RTL_REGISTRY_OPTIONAL, hDeviceKey, queryTable, NULL, NULL ); if (NT_SUCCESS(status)) { // // Promote manually specified priorities over inferred ones. // Note that we _add_ in 0x80000000 rather than _or_ it in. // This lets us wrap in case we ever need to specify a priority // lower than what's inferred. // dockDepth += 0x80000000; } ZwClose(hDeviceKey); } if (NT_SUCCESS(status) || (curDock == DeviceNode)) { break; } curDock = DeviceNode; } if (!NT_SUCCESS(status)) { // // If we can find no eject preference order, use the depth of the // dock devnode. // dockDepth = realDock ? realDock->Level : DeviceNode->Level; } if (realDock) { ObDereferenceObject(realDock->PhysicalDeviceObject); } // // The best dock is selected as the dock with the deepest ejected device. // if ((pBestDock->PhysicalDeviceObject == NULL) || (dockDepth > pBestDock->Depth)) { if (pBestDock->PhysicalDeviceObject) { ObDereferenceObject(pBestDock->PhysicalDeviceObject); } pBestDock->PhysicalDeviceObject = DeviceNode->PhysicalDeviceObject; pBestDock->Depth = dockDepth; ObReferenceObject(pBestDock->PhysicalDeviceObject); } // // Continue enumerating. // return STATUS_SUCCESS; } NTSTATUS PiProfileUpdateDeviceTree( VOID ) /*++ Routine Description: This function is called after the system has transitioned into a new hardware profile. The thread from which it is called may be holding an enumeration lock. Calling this function does two tasks: 1) If a disabled devnode in the tree should be enabled in this new hardware profile state, it will be started. 2) If an enabled devnode in the tree should be disabled in this new hardware profile state, it will be (surprise) removed. ADRIAO N.B. 02/19/1999 - Why surprise remove? There are four cases to be handled: a) Dock disappearing, need to enable device in new profile b) Dock appearing, need to enable device in new profile c) Dock disappearing, need to disable device in new profile d) Dock appearing, need to disable device in new profile a) and b) are trivial. c) involves treating the appropriate devices as if they were in the removal relation lists for the dock. d) is another matter altogether as we need to query-remove/remove devices before starting another. NT5's PnP state machine cannot handle this, so for this release we cleanup rather hastily after the profile change. Parameters: NONE. Return Value: NTSTATUS. --*/ { PWORK_QUEUE_ITEM workQueueItem; PAGED_CODE(); workQueueItem = (PWORK_QUEUE_ITEM) ExAllocatePool( NonPagedPool, sizeof(WORK_QUEUE_ITEM) ); if (workQueueItem) { // // Queue this up so we can walk the tree outside of the enumeration lock. // ExInitializeWorkItem( workQueueItem, PiProfileUpdateDeviceTreeWorker, workQueueItem ); ExQueueWorkItem( workQueueItem, CriticalWorkQueue ); return STATUS_SUCCESS; } else { return STATUS_INSUFFICIENT_RESOURCES; } } VOID PiProfileUpdateDeviceTreeWorker( IN PVOID Context ) /*++ Routine Description: This function is called on a work thread by PiProfileUpdateDeviceTree when the system has transitioned to a new hardware profile. Parameters: NONE. Return Value: NONE. --*/ { PAGED_CODE(); PpDevNodeLockTree(PPL_TREEOP_ALLOW_READS); PipForAllDeviceNodes(PiProfileUpdateDeviceTreeCallback, NULL); PpDevNodeUnlockTree(PPL_TREEOP_ALLOW_READS); ExFreePool(Context); } NTSTATUS PiProfileUpdateDeviceTreeCallback( IN PDEVICE_NODE DeviceNode, IN PVOID Context ) /*++ Routine Description: This function is called for each devnode after the system has transitioned hardware profile states. Parameters: NONE. Return Value: NONE. --*/ { PDEVICE_NODE parentDevNode; UNREFERENCED_PARAMETER( Context ); PAGED_CODE(); if (DeviceNode->State == DeviceNodeStarted) { // // Calling this function will disable the device if it is appropriate // to do so. // if (!IopIsDeviceInstanceEnabled(NULL, &DeviceNode->InstancePath, FALSE)) { PipRequestDeviceRemoval(DeviceNode, FALSE, CM_PROB_DISABLED); } } else if (((DeviceNode->State == DeviceNodeInitialized) || (DeviceNode->State == DeviceNodeRemoved)) && PipIsDevNodeProblem(DeviceNode, CM_PROB_DISABLED)) { // // We might be turning on the device. So we will clear the problem // flags iff the device problem was CM_PROB_DISABLED. We must clear the // problem code or otherwise IopIsDeviceInstanceEnabled will ignore us. // PipClearDevNodeProblem(DeviceNode); // // Make sure the device stays down iff appropriate. // if (IopIsDeviceInstanceEnabled(NULL, &DeviceNode->InstancePath, FALSE)) { // // This device should come back online. Bring it out of the // removed state and queue up an enumeration at the parent level // to resurrect him. // IopRestartDeviceNode(DeviceNode); parentDevNode = DeviceNode->Parent; IoInvalidateDeviceRelations( parentDevNode->PhysicalDeviceObject, BusRelations ); } else { // // Restore the problem code. // PipSetDevNodeProblem(DeviceNode, CM_PROB_DISABLED); } } else { ASSERT((!PipIsDevNodeProblem(DeviceNode, CM_PROB_DISABLED)) || ((DeviceNode->State == DeviceNodeAwaitingQueuedRemoval) || (DeviceNode->State == DeviceNodeAwaitingQueuedDeletion))); } return STATUS_SUCCESS; }