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
PiInitializeNotifications(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
PiDereferencePnpNotifyEntry(_In_ PPNP_NOTIFY_ENTRY Entry)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
PiReferencePnpNotifyEntry(_In_ PPNP_NOTIFY_ENTRY Entry)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
PiCallNotifyProc(_In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE Proc,_In_ PVOID NotificationStructure,_In_ PVOID Context)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")
_Requires_lock_held_(Lock)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
PiNotifyDeviceInterfaceChange(_In_ LPCGUID Event,_In_ LPCGUID InterfaceClassGuid,_In_ PUNICODE_STRING SymbolicLinkName)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
PiNotifyHardwareProfileChange(_In_ LPCGUID Event)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
PiNotifyTargetDeviceChange(_In_ LPCGUID Event,_In_ PDEVICE_OBJECT DeviceObject,_In_opt_ PTARGET_DEVICE_CUSTOM_NOTIFICATION CustomNotification)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
IoPnPDeliverServicePowerNotification(_In_ ULONG VetoedPowerOperation,_In_ ULONG PowerNotificationCode,_In_ ULONG PowerNotificationData,_In_ BOOLEAN Synchronous)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
IoRegisterPlugPlayNotification(_In_ IO_NOTIFICATION_EVENT_CATEGORY EventCategory,_In_ ULONG EventCategoryFlags,_In_opt_ PVOID EventCategoryData,_In_ PDRIVER_OBJECT DriverObject,_In_ PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,_Inout_opt_ PVOID Context,_Out_ PVOID * NotificationEntry)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
IoUnregisterPlugPlayNotification(_In_ PVOID NotificationEntry)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