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