1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Plug & Play notification functions 5 * COPYRIGHT: Copyright 2003 Filip Navara <xnavara@volny.cz> 6 * Copyright 2005-2006 Hervé Poussineau <hpoussin@reactos.org> 7 * Copyright 2010 Pierre Schweitzer <pierre@reactos.org> 8 * Copyright 2020 Victor Perevertkin <victor.perevertkin@reactos.org> 9 */ 10 11 /* INCLUDES ******************************************************************/ 12 13 #include <ntoskrnl.h> 14 #define NDEBUG 15 #include <debug.h> 16 17 /* DATA **********************************************************************/ 18 19 KGUARDED_MUTEX PiNotifyTargetDeviceLock; 20 KGUARDED_MUTEX PiNotifyHwProfileLock; 21 KGUARDED_MUTEX PiNotifyDeviceInterfaceLock; 22 23 _Guarded_by_(PiNotifyHwProfileLock) 24 LIST_ENTRY PiNotifyHwProfileListHead; 25 26 _Guarded_by_(PiNotifyDeviceInterfaceLock) 27 LIST_ENTRY PiNotifyDeviceInterfaceListHead; 28 29 /* TYPES *********************************************************************/ 30 31 typedef struct _PNP_NOTIFY_ENTRY 32 { 33 LIST_ENTRY PnpNotifyList; 34 PVOID Context; 35 PDRIVER_OBJECT DriverObject; 36 PDRIVER_NOTIFICATION_CALLBACK_ROUTINE PnpNotificationProc; 37 union 38 { 39 GUID Guid; // for EventCategoryDeviceInterfaceChange 40 struct 41 { 42 PFILE_OBJECT FileObject; // for EventCategoryTargetDeviceChange 43 PDEVICE_OBJECT DeviceObject; 44 }; 45 }; 46 IO_NOTIFICATION_EVENT_CATEGORY EventCategory; 47 UINT8 RefCount; 48 BOOLEAN Deleted; 49 } PNP_NOTIFY_ENTRY, *PPNP_NOTIFY_ENTRY; 50 51 /* FUNCTIONS *****************************************************************/ 52 53 CODE_SEG("INIT") 54 VOID 55 PiInitializeNotifications(VOID) 56 { 57 KeInitializeGuardedMutex(&PiNotifyTargetDeviceLock); 58 KeInitializeGuardedMutex(&PiNotifyHwProfileLock); 59 KeInitializeGuardedMutex(&PiNotifyDeviceInterfaceLock); 60 InitializeListHead(&PiNotifyHwProfileListHead); 61 InitializeListHead(&PiNotifyDeviceInterfaceListHead); 62 } 63 64 static 65 CODE_SEG("PAGE") 66 VOID 67 PiDereferencePnpNotifyEntry( 68 _In_ PPNP_NOTIFY_ENTRY Entry) 69 { 70 PAGED_CODE(); 71 ASSERT(Entry->RefCount > 0); 72 73 ObDereferenceObject(Entry->DriverObject); 74 Entry->RefCount--; 75 if (Entry->RefCount == 0) 76 { 77 ASSERT(Entry->Deleted); 78 79 RemoveEntryList(&Entry->PnpNotifyList); 80 if (Entry->EventCategory == EventCategoryTargetDeviceChange) 81 { 82 // IopGetRelatedTargetDevice referenced the device upon the notification registration 83 ObDereferenceObject(Entry->DeviceObject); 84 } 85 ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY); 86 } 87 } 88 89 static 90 CODE_SEG("PAGE") 91 VOID 92 PiReferencePnpNotifyEntry( 93 _In_ PPNP_NOTIFY_ENTRY Entry) 94 { 95 PAGED_CODE(); 96 ObReferenceObject(Entry->DriverObject); 97 Entry->RefCount++; 98 } 99 100 /** 101 * @brief Calls PnP notification routine and makes some checks to detect faulty drivers 102 */ 103 static 104 CODE_SEG("PAGE") 105 VOID 106 PiCallNotifyProc( 107 _In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE Proc, 108 _In_ PVOID NotificationStructure, 109 _In_ PVOID Context) 110 { 111 PAGED_CODE(); 112 #if DBG 113 KIRQL oldIrql = KeGetCurrentIrql(); 114 ULONG oldApcDisable = KeGetCurrentThread()->CombinedApcDisable; 115 #endif 116 117 Proc(NotificationStructure, Context); 118 119 ASSERT(oldIrql == KeGetCurrentIrql() && 120 oldApcDisable == KeGetCurrentThread()->CombinedApcDisable); 121 } 122 123 static 124 CODE_SEG("PAGE") 125 _Requires_lock_held_(Lock) 126 VOID 127 PiProcessSingleNotification( 128 _In_ PPNP_NOTIFY_ENTRY Entry, 129 _In_ PVOID NotificationStructure, 130 _In_ PKGUARDED_MUTEX Lock, 131 _Out_ PLIST_ENTRY *NextEntry) 132 { 133 PAGED_CODE(); 134 135 // the notification may be unregistered inside the procedure 136 // thus reference the entry so we may proceed 137 PiReferencePnpNotifyEntry(Entry); 138 139 // release the lock because the notification routine has to be called without any 140 // limitations regarding APCs 141 KeReleaseGuardedMutex(Lock); 142 PiCallNotifyProc(Entry->PnpNotificationProc, NotificationStructure, Entry->Context); 143 KeAcquireGuardedMutex(Lock); 144 145 // take the next entry link only after the callback finishes 146 // the lock is not held there, so Entry may have changed at this point 147 *NextEntry = Entry->PnpNotifyList.Flink; 148 PiDereferencePnpNotifyEntry(Entry); 149 } 150 151 /** 152 * @brief Delivers the event to all drivers subscribed to 153 * EventCategoryDeviceInterfaceChange 154 * 155 * @param[in] Event The PnP event GUID 156 * @param[in] InterfaceClassGuid The GUID of an interface class 157 * @param[in] SymbolicLinkName Pointer to a string identifying the device interface name 158 */ 159 CODE_SEG("PAGE") 160 VOID 161 PiNotifyDeviceInterfaceChange( 162 _In_ LPCGUID Event, 163 _In_ LPCGUID InterfaceClassGuid, 164 _In_ PUNICODE_STRING SymbolicLinkName) 165 { 166 PAGED_CODE(); 167 168 PDEVICE_INTERFACE_CHANGE_NOTIFICATION notifyStruct; 169 notifyStruct = ExAllocatePoolWithTag(PagedPool, sizeof(*notifyStruct), TAG_PNP_NOTIFY); 170 if (!notifyStruct) 171 { 172 return; 173 } 174 175 *notifyStruct = (DEVICE_INTERFACE_CHANGE_NOTIFICATION) { 176 .Version = 1, 177 .Size = sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION), 178 .Event = *Event, 179 .InterfaceClassGuid = *InterfaceClassGuid, 180 .SymbolicLinkName = SymbolicLinkName 181 }; 182 183 DPRINT("Delivering a DeviceInterfaceChange PnP event\n"); 184 185 KeAcquireGuardedMutex(&PiNotifyDeviceInterfaceLock); 186 187 PLIST_ENTRY entry = PiNotifyDeviceInterfaceListHead.Flink; 188 while (entry != &PiNotifyDeviceInterfaceListHead) 189 { 190 PPNP_NOTIFY_ENTRY nEntry = CONTAINING_RECORD(entry, PNP_NOTIFY_ENTRY, PnpNotifyList); 191 192 if (!IsEqualGUID(¬ifyStruct->InterfaceClassGuid, &nEntry->Guid)) 193 { 194 entry = entry->Flink; 195 continue; 196 } 197 198 PiProcessSingleNotification(nEntry, notifyStruct, &PiNotifyDeviceInterfaceLock, &entry); 199 } 200 201 KeReleaseGuardedMutex(&PiNotifyDeviceInterfaceLock); 202 ExFreePoolWithTag(notifyStruct, TAG_PNP_NOTIFY); 203 } 204 205 206 /** 207 * @brief Delivers the event to all drivers subscribed to 208 * EventCategoryHardwareProfileChange PnP event 209 * 210 * @param[in] Event The PnP event GUID 211 */ 212 CODE_SEG("PAGE") 213 VOID 214 PiNotifyHardwareProfileChange( 215 _In_ LPCGUID Event) 216 { 217 PAGED_CODE(); 218 219 PHWPROFILE_CHANGE_NOTIFICATION notifyStruct; 220 notifyStruct = ExAllocatePoolWithTag(PagedPool, sizeof(*notifyStruct), TAG_PNP_NOTIFY); 221 if (!notifyStruct) 222 { 223 return; 224 } 225 226 *notifyStruct = (HWPROFILE_CHANGE_NOTIFICATION) { 227 .Version = 1, 228 .Size = sizeof(HWPROFILE_CHANGE_NOTIFICATION), 229 .Event = *Event 230 }; 231 232 DPRINT("Delivering a HardwareProfileChange PnP event\n"); 233 234 KeAcquireGuardedMutex(&PiNotifyHwProfileLock); 235 236 PLIST_ENTRY entry = PiNotifyHwProfileListHead.Flink; 237 while (entry != &PiNotifyHwProfileListHead) 238 { 239 PPNP_NOTIFY_ENTRY nEntry = CONTAINING_RECORD(entry, PNP_NOTIFY_ENTRY, PnpNotifyList); 240 241 PiProcessSingleNotification(nEntry, notifyStruct, &PiNotifyHwProfileLock, &entry); 242 } 243 244 KeReleaseGuardedMutex(&PiNotifyHwProfileLock); 245 ExFreePoolWithTag(notifyStruct, TAG_PNP_NOTIFY); 246 } 247 248 /** 249 * @brief Delivers the event to all drivers subscribed to 250 * EventCategoryTargetDeviceChange PnP event 251 * 252 * @param[in] Event The PnP event GUID 253 * @param[in] DeviceObject The (target) device object 254 * @param[in] CustomNotification Pointer to a custom notification for GUID_PNP_CUSTOM_NOTIFICATION 255 */ 256 CODE_SEG("PAGE") 257 VOID 258 PiNotifyTargetDeviceChange( 259 _In_ LPCGUID Event, 260 _In_ PDEVICE_OBJECT DeviceObject, 261 _In_opt_ PTARGET_DEVICE_CUSTOM_NOTIFICATION CustomNotification) 262 { 263 PAGED_CODE(); 264 265 PVOID notificationStruct; 266 // just in case our device is removed during the operation 267 ObReferenceObject(DeviceObject); 268 269 PDEVICE_NODE deviceNode = IopGetDeviceNode(DeviceObject); 270 ASSERT(deviceNode); 271 272 if (!IsEqualGUID(Event, &GUID_PNP_CUSTOM_NOTIFICATION)) 273 { 274 PTARGET_DEVICE_REMOVAL_NOTIFICATION notifStruct; 275 notifStruct = ExAllocatePoolWithTag(PagedPool, sizeof(*notifStruct), TAG_PNP_NOTIFY); 276 if (!notifStruct) 277 { 278 return; 279 } 280 281 *notifStruct = (TARGET_DEVICE_REMOVAL_NOTIFICATION) { 282 .Version = 1, 283 .Size = sizeof(TARGET_DEVICE_REMOVAL_NOTIFICATION), 284 .Event = *Event 285 }; 286 287 notificationStruct = notifStruct; 288 289 DPRINT("Delivering a (non-custom) TargetDeviceChange PnP event\n"); 290 } 291 else 292 { 293 ASSERT(CustomNotification); 294 // assuming everythng else is correct 295 296 notificationStruct = CustomNotification; 297 298 DPRINT("Delivering a (custom) TargetDeviceChange PnP event\n"); 299 } 300 301 KeAcquireGuardedMutex(&PiNotifyTargetDeviceLock); 302 303 PLIST_ENTRY entry = deviceNode->TargetDeviceNotify.Flink; 304 while (entry != &deviceNode->TargetDeviceNotify) 305 { 306 PPNP_NOTIFY_ENTRY nEntry = CONTAINING_RECORD(entry, PNP_NOTIFY_ENTRY, PnpNotifyList); 307 308 // put the file object from our saved entry to this particular notification's struct 309 ((PTARGET_DEVICE_REMOVAL_NOTIFICATION)notificationStruct)->FileObject = nEntry->FileObject; 310 // so you don't need to look at the definition ;) 311 C_ASSERT(FIELD_OFFSET(TARGET_DEVICE_REMOVAL_NOTIFICATION, FileObject) 312 == FIELD_OFFSET(TARGET_DEVICE_CUSTOM_NOTIFICATION, FileObject)); 313 314 PiProcessSingleNotification(nEntry, notificationStruct, &PiNotifyTargetDeviceLock, &entry); 315 } 316 317 KeReleaseGuardedMutex(&PiNotifyTargetDeviceLock); 318 if (notificationStruct != CustomNotification) 319 ExFreePoolWithTag(notificationStruct, TAG_PNP_NOTIFY); 320 ObDereferenceObject(DeviceObject); 321 } 322 323 /* PUBLIC FUNCTIONS **********************************************************/ 324 325 /* 326 * @unimplemented 327 */ 328 ULONG 329 NTAPI 330 IoPnPDeliverServicePowerNotification( 331 _In_ ULONG VetoedPowerOperation, 332 _In_ ULONG PowerNotificationCode, 333 _In_ ULONG PowerNotificationData, 334 _In_ BOOLEAN Synchronous) 335 { 336 UNIMPLEMENTED; 337 return 0; 338 } 339 340 /* 341 * @implemented 342 */ 343 CODE_SEG("PAGE") 344 NTSTATUS 345 NTAPI 346 IoRegisterPlugPlayNotification( 347 _In_ IO_NOTIFICATION_EVENT_CATEGORY EventCategory, 348 _In_ ULONG EventCategoryFlags, 349 _In_opt_ PVOID EventCategoryData, 350 _In_ PDRIVER_OBJECT DriverObject, 351 _In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine, 352 _Inout_opt_ PVOID Context, 353 _Out_ PVOID *NotificationEntry) 354 { 355 PPNP_NOTIFY_ENTRY Entry; 356 PWSTR SymbolicLinkList; 357 NTSTATUS Status; 358 PAGED_CODE(); 359 360 DPRINT("%s(EventCategory 0x%x, EventCategoryFlags 0x%lx, DriverObject %p) called.\n", 361 __FUNCTION__, EventCategory, EventCategoryFlags, DriverObject); 362 363 ObReferenceObject(DriverObject); 364 365 /* Try to allocate entry for notification before sending any notification */ 366 Entry = ExAllocatePoolWithTag(PagedPool, sizeof(PNP_NOTIFY_ENTRY), TAG_PNP_NOTIFY); 367 if (!Entry) 368 { 369 DPRINT("ExAllocatePool() failed\n"); 370 ObDereferenceObject(DriverObject); 371 return STATUS_INSUFFICIENT_RESOURCES; 372 } 373 374 *Entry = (PNP_NOTIFY_ENTRY) { 375 .PnpNotificationProc = CallbackRoutine, 376 .Context = Context, 377 .DriverObject = DriverObject, 378 .EventCategory = EventCategory, 379 .RefCount = 1 380 }; 381 382 switch (EventCategory) 383 { 384 case EventCategoryDeviceInterfaceChange: 385 { 386 Entry->Guid = *(LPGUID)EventCategoryData; 387 388 // first register the notification 389 KeAcquireGuardedMutex(&PiNotifyDeviceInterfaceLock); 390 InsertTailList(&PiNotifyDeviceInterfaceListHead, &Entry->PnpNotifyList); 391 KeReleaseGuardedMutex(&PiNotifyDeviceInterfaceLock); 392 393 // then process existing interfaces if asked 394 if (EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES) 395 { 396 DEVICE_INTERFACE_CHANGE_NOTIFICATION NotificationInfos; 397 UNICODE_STRING SymbolicLinkU; 398 PWSTR SymbolicLink; 399 400 Status = IoGetDeviceInterfaces((LPGUID)EventCategoryData, 401 NULL, /* PhysicalDeviceObject OPTIONAL */ 402 0, /* Flags */ 403 &SymbolicLinkList); 404 if (NT_SUCCESS(Status)) 405 { 406 /* Enumerate SymbolicLinkList */ 407 NotificationInfos.Version = 1; 408 NotificationInfos.Size = sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION); 409 NotificationInfos.Event = GUID_DEVICE_INTERFACE_ARRIVAL; 410 NotificationInfos.InterfaceClassGuid = *(LPGUID)EventCategoryData; 411 NotificationInfos.SymbolicLinkName = &SymbolicLinkU; 412 413 for (SymbolicLink = SymbolicLinkList; 414 *SymbolicLink; 415 SymbolicLink += (SymbolicLinkU.Length / sizeof(WCHAR)) + 1) 416 { 417 RtlInitUnicodeString(&SymbolicLinkU, SymbolicLink); 418 DPRINT("Calling callback routine for %S\n", SymbolicLink); 419 PiCallNotifyProc(CallbackRoutine, &NotificationInfos, Context); 420 } 421 422 ExFreePool(SymbolicLinkList); 423 } 424 } 425 break; 426 } 427 case EventCategoryHardwareProfileChange: 428 { 429 KeAcquireGuardedMutex(&PiNotifyHwProfileLock); 430 InsertTailList(&PiNotifyHwProfileListHead, &Entry->PnpNotifyList); 431 KeReleaseGuardedMutex(&PiNotifyHwProfileLock); 432 break; 433 } 434 case EventCategoryTargetDeviceChange: 435 { 436 PDEVICE_NODE deviceNode; 437 Entry->FileObject = (PFILE_OBJECT)EventCategoryData; 438 439 // NOTE: the device node's PDO is referenced here 440 Status = IopGetRelatedTargetDevice(Entry->FileObject, &deviceNode); 441 if (!NT_SUCCESS(Status)) 442 { 443 ObDereferenceObject(DriverObject); 444 ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY); 445 return Status; 446 } 447 // save it so we can dereference it later 448 Entry->DeviceObject = deviceNode->PhysicalDeviceObject; 449 450 // each DEVICE_NODE has its own registered notifications list 451 KeAcquireGuardedMutex(&PiNotifyTargetDeviceLock); 452 InsertTailList(&deviceNode->TargetDeviceNotify, &Entry->PnpNotifyList); 453 KeReleaseGuardedMutex(&PiNotifyTargetDeviceLock); 454 break; 455 } 456 default: 457 { 458 DPRINT1("%s: unknown EventCategory 0x%x UNIMPLEMENTED\n", 459 __FUNCTION__, EventCategory); 460 461 ObDereferenceObject(DriverObject); 462 ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY); 463 return STATUS_NOT_SUPPORTED; 464 } 465 } 466 467 DPRINT("%s returns NotificationEntry %p\n", __FUNCTION__, Entry); 468 469 *NotificationEntry = Entry; 470 471 return STATUS_SUCCESS; 472 } 473 474 /* 475 * @implemented 476 */ 477 CODE_SEG("PAGE") 478 NTSTATUS 479 NTAPI 480 IoUnregisterPlugPlayNotification( 481 _In_ PVOID NotificationEntry) 482 { 483 PPNP_NOTIFY_ENTRY Entry = NotificationEntry; 484 PKGUARDED_MUTEX Lock; 485 486 PAGED_CODE(); 487 488 DPRINT("%s(NotificationEntry %p) called\n", __FUNCTION__, Entry); 489 490 switch (Entry->EventCategory) 491 { 492 case EventCategoryDeviceInterfaceChange: 493 Lock = &PiNotifyDeviceInterfaceLock; 494 break; 495 case EventCategoryHardwareProfileChange: 496 Lock = &PiNotifyHwProfileLock; 497 break; 498 case EventCategoryTargetDeviceChange: 499 Lock = &PiNotifyTargetDeviceLock; 500 break; 501 default: 502 UNREACHABLE; 503 return STATUS_NOT_SUPPORTED; 504 } 505 506 KeAcquireGuardedMutex(Lock); 507 if (!Entry->Deleted) 508 { 509 Entry->Deleted = TRUE; // so it can't be unregistered two times 510 PiDereferencePnpNotifyEntry(Entry); 511 } 512 else 513 { 514 DPRINT1("IoUnregisterPlugPlayNotification called two times for 0x%p\n", NotificationEntry); 515 } 516 KeReleaseGuardedMutex(Lock); 517 518 return STATUS_SUCCESS; 519 } 520