xref: /reactos/ntoskrnl/io/pnpmgr/pnpnotify.c (revision 1d18b12f)
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(&notifyStruct->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