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 ExFreePoolWithTag(notificationStruct, TAG_PNP_NOTIFY); 319 ObDereferenceObject(DeviceObject); 320 } 321 322 /* PUBLIC FUNCTIONS **********************************************************/ 323 324 /* 325 * @unimplemented 326 */ 327 ULONG 328 NTAPI 329 IoPnPDeliverServicePowerNotification( 330 _In_ ULONG VetoedPowerOperation, 331 _In_ ULONG PowerNotificationCode, 332 _In_ ULONG PowerNotificationData, 333 _In_ BOOLEAN Synchronous) 334 { 335 UNIMPLEMENTED; 336 return 0; 337 } 338 339 /* 340 * @implemented 341 */ 342 CODE_SEG("PAGE") 343 NTSTATUS 344 NTAPI 345 IoRegisterPlugPlayNotification( 346 _In_ IO_NOTIFICATION_EVENT_CATEGORY EventCategory, 347 _In_ ULONG EventCategoryFlags, 348 _In_opt_ PVOID EventCategoryData, 349 _In_ PDRIVER_OBJECT DriverObject, 350 _In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine, 351 _Inout_opt_ PVOID Context, 352 _Out_ PVOID *NotificationEntry) 353 { 354 PPNP_NOTIFY_ENTRY Entry; 355 PWSTR SymbolicLinkList; 356 NTSTATUS Status; 357 PAGED_CODE(); 358 359 DPRINT("%s(EventCategory 0x%x, EventCategoryFlags 0x%lx, DriverObject %p) called.\n", 360 __FUNCTION__, EventCategory, EventCategoryFlags, DriverObject); 361 362 ObReferenceObject(DriverObject); 363 364 /* Try to allocate entry for notification before sending any notification */ 365 Entry = ExAllocatePoolWithTag(PagedPool, sizeof(PNP_NOTIFY_ENTRY), TAG_PNP_NOTIFY); 366 if (!Entry) 367 { 368 DPRINT("ExAllocatePool() failed\n"); 369 ObDereferenceObject(DriverObject); 370 return STATUS_INSUFFICIENT_RESOURCES; 371 } 372 373 *Entry = (PNP_NOTIFY_ENTRY) { 374 .PnpNotificationProc = CallbackRoutine, 375 .Context = Context, 376 .DriverObject = DriverObject, 377 .EventCategory = EventCategory, 378 .RefCount = 1 379 }; 380 381 switch (EventCategory) 382 { 383 case EventCategoryDeviceInterfaceChange: 384 { 385 Entry->Guid = *(LPGUID)EventCategoryData; 386 387 // first register the notification 388 KeAcquireGuardedMutex(&PiNotifyDeviceInterfaceLock); 389 InsertTailList(&PiNotifyDeviceInterfaceListHead, &Entry->PnpNotifyList); 390 KeReleaseGuardedMutex(&PiNotifyDeviceInterfaceLock); 391 392 // then process existing interfaces if asked 393 if (EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES) 394 { 395 DEVICE_INTERFACE_CHANGE_NOTIFICATION NotificationInfos; 396 UNICODE_STRING SymbolicLinkU; 397 PWSTR SymbolicLink; 398 399 Status = IoGetDeviceInterfaces((LPGUID)EventCategoryData, 400 NULL, /* PhysicalDeviceObject OPTIONAL */ 401 0, /* Flags */ 402 &SymbolicLinkList); 403 if (NT_SUCCESS(Status)) 404 { 405 /* Enumerate SymbolicLinkList */ 406 NotificationInfos.Version = 1; 407 NotificationInfos.Size = sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION); 408 NotificationInfos.Event = GUID_DEVICE_INTERFACE_ARRIVAL; 409 NotificationInfos.InterfaceClassGuid = *(LPGUID)EventCategoryData; 410 NotificationInfos.SymbolicLinkName = &SymbolicLinkU; 411 412 for (SymbolicLink = SymbolicLinkList; 413 *SymbolicLink; 414 SymbolicLink += (SymbolicLinkU.Length / sizeof(WCHAR)) + 1) 415 { 416 RtlInitUnicodeString(&SymbolicLinkU, SymbolicLink); 417 DPRINT("Calling callback routine for %S\n", SymbolicLink); 418 PiCallNotifyProc(CallbackRoutine, &NotificationInfos, Context); 419 } 420 421 ExFreePool(SymbolicLinkList); 422 } 423 } 424 break; 425 } 426 case EventCategoryHardwareProfileChange: 427 { 428 KeAcquireGuardedMutex(&PiNotifyHwProfileLock); 429 InsertTailList(&PiNotifyHwProfileListHead, &Entry->PnpNotifyList); 430 KeReleaseGuardedMutex(&PiNotifyHwProfileLock); 431 break; 432 } 433 case EventCategoryTargetDeviceChange: 434 { 435 PDEVICE_NODE deviceNode; 436 Entry->FileObject = (PFILE_OBJECT)EventCategoryData; 437 438 // NOTE: the device node's PDO is referenced here 439 Status = IopGetRelatedTargetDevice(Entry->FileObject, &deviceNode); 440 if (!NT_SUCCESS(Status)) 441 { 442 ObDereferenceObject(DriverObject); 443 ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY); 444 return Status; 445 } 446 // save it so we can dereference it later 447 Entry->DeviceObject = deviceNode->PhysicalDeviceObject; 448 449 // each DEVICE_NODE has its own registered notifications list 450 KeAcquireGuardedMutex(&PiNotifyTargetDeviceLock); 451 InsertTailList(&deviceNode->TargetDeviceNotify, &Entry->PnpNotifyList); 452 KeReleaseGuardedMutex(&PiNotifyTargetDeviceLock); 453 break; 454 } 455 default: 456 { 457 DPRINT1("%s: unknown EventCategory 0x%x UNIMPLEMENTED\n", 458 __FUNCTION__, EventCategory); 459 460 ObDereferenceObject(DriverObject); 461 ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY); 462 return STATUS_NOT_SUPPORTED; 463 } 464 } 465 466 DPRINT("%s returns NotificationEntry %p\n", __FUNCTION__, Entry); 467 468 *NotificationEntry = Entry; 469 470 return STATUS_SUCCESS; 471 } 472 473 /* 474 * @implemented 475 */ 476 CODE_SEG("PAGE") 477 NTSTATUS 478 NTAPI 479 IoUnregisterPlugPlayNotification( 480 _In_ PVOID NotificationEntry) 481 { 482 PPNP_NOTIFY_ENTRY Entry = NotificationEntry; 483 PKGUARDED_MUTEX Lock; 484 485 PAGED_CODE(); 486 487 DPRINT("%s(NotificationEntry %p) called\n", __FUNCTION__, Entry); 488 489 switch (Entry->EventCategory) 490 { 491 case EventCategoryDeviceInterfaceChange: 492 Lock = &PiNotifyDeviceInterfaceLock; 493 break; 494 case EventCategoryHardwareProfileChange: 495 Lock = &PiNotifyHwProfileLock; 496 break; 497 case EventCategoryTargetDeviceChange: 498 Lock = &PiNotifyTargetDeviceLock; 499 break; 500 default: 501 UNREACHABLE; 502 return STATUS_NOT_SUPPORTED; 503 } 504 505 KeAcquireGuardedMutex(Lock); 506 if (!Entry->Deleted) 507 { 508 Entry->Deleted = TRUE; // so it can't be unregistered two times 509 PiDereferencePnpNotifyEntry(Entry); 510 } 511 else 512 { 513 DPRINT1("IoUnregisterPlugPlayNotification called two times for 0x%p\n", NotificationEntry); 514 } 515 KeReleaseGuardedMutex(Lock); 516 517 return STATUS_SUCCESS; 518 } 519