xref: /reactos/ntoskrnl/ex/callback.c (revision 1734f297)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/ex/callback.c
5  * PURPOSE:         Executive callbacks
6  * PROGRAMMERS:     Alex Ionescu (alex.ionescu@reactos.org)
7  */
8 
9 /* INCLUDES ******************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* TYPES *********************************************************************/
16 
17 /* Mapping for Callback Object */
18 GENERIC_MAPPING ExpCallbackMapping =
19 {
20     STANDARD_RIGHTS_READ,
21     STANDARD_RIGHTS_WRITE | CALLBACK_MODIFY_STATE,
22     STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
23     CALLBACK_ALL_ACCESS
24 };
25 
26 /* Kernel Default Callbacks */
27 PCALLBACK_OBJECT SetSystemTimeCallback;
28 PCALLBACK_OBJECT SetSystemStateCallback;
29 PCALLBACK_OBJECT PowerStateCallback;
30 SYSTEM_CALLBACKS ExpInitializeCallback[] =
31 {
32    {&SetSystemTimeCallback, L"\\Callback\\SetSystemTime"},
33    {&SetSystemStateCallback, L"\\Callback\\SetSystemState"},
34    {&PowerStateCallback, L"\\Callback\\PowerState"},
35    {NULL, NULL}
36 };
37 
38 POBJECT_TYPE ExCallbackObjectType;
39 KEVENT ExpCallbackEvent;
40 EX_PUSH_LOCK ExpCallBackFlush;
41 
42 /* PRIVATE FUNCTIONS *********************************************************/
43 
44 VOID
45 NTAPI
46 ExInitializeCallBack(IN OUT PEX_CALLBACK Callback)
47 {
48     /* Initialize the fast reference */
49     ExInitializeFastReference(&Callback->RoutineBlock, NULL);
50 }
51 
52 PEX_CALLBACK_ROUTINE_BLOCK
53 NTAPI
54 ExAllocateCallBack(IN PEX_CALLBACK_FUNCTION Function,
55                    IN PVOID Context)
56 {
57     PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock;
58 
59     /* Allocate a callback */
60     CallbackBlock = ExAllocatePoolWithTag(PagedPool,
61                                           sizeof(EX_CALLBACK_ROUTINE_BLOCK),
62                                           TAG_CALLBACK_ROUTINE_BLOCK);
63     if (CallbackBlock)
64     {
65         /* Initialize it */
66         CallbackBlock->Function = Function;
67         CallbackBlock->Context = Context;
68         ExInitializeRundownProtection(&CallbackBlock->RundownProtect);
69     }
70 
71     /* Return it */
72     return CallbackBlock;
73 }
74 
75 VOID
76 NTAPI
77 ExFreeCallBack(IN PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock)
78 {
79     /* Just free it from memory */
80     ExFreePoolWithTag(CallbackBlock, TAG_CALLBACK_ROUTINE_BLOCK);
81 }
82 
83 VOID
84 NTAPI
85 ExWaitForCallBacks(IN PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock)
86 {
87     /* Wait on the rundown */
88     ExWaitForRundownProtectionRelease(&CallbackBlock->RundownProtect);
89 }
90 
91 PEX_CALLBACK_FUNCTION
92 NTAPI
93 ExGetCallBackBlockRoutine(IN PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock)
94 {
95     /* Return the function */
96     return CallbackBlock->Function;
97 }
98 
99 PVOID
100 NTAPI
101 ExGetCallBackBlockContext(IN PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock)
102 {
103     /* Return the context */
104     return CallbackBlock->Context;
105 }
106 
107 VOID
108 NTAPI
109 ExDereferenceCallBackBlock(IN OUT PEX_CALLBACK CallBack,
110                            IN PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock)
111 {
112     /* Release a fast reference */
113     if (!ExReleaseFastReference(&CallBack->RoutineBlock, CallbackBlock))
114     {
115         /* Take slow path */
116         ExReleaseRundownProtection(&CallbackBlock->RundownProtect);
117     }
118 }
119 
120 PEX_CALLBACK_ROUTINE_BLOCK
121 NTAPI
122 ExReferenceCallBackBlock(IN OUT PEX_CALLBACK CallBack)
123 {
124     EX_FAST_REF OldValue;
125     ULONG_PTR Count;
126     PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock;
127 
128     /* Acquire a reference */
129     OldValue = ExAcquireFastReference(&CallBack->RoutineBlock);
130     Count = ExGetCountFastReference(OldValue);
131 
132     /* Fail if there isn't any object */
133     if (!ExGetObjectFastReference(OldValue)) return NULL;
134 
135     /* Check if we don't have a reference */
136     if (!Count)
137     {
138         /* FIXME: Race */
139         DPRINT1("Unhandled callback race condition\n");
140         ASSERT(FALSE);
141         return NULL;
142     }
143 
144     /* Get the callback block */
145     CallbackBlock = ExGetObjectFastReference(OldValue);
146 
147     /* Check if this is the last reference */
148     if (Count == 1)
149     {
150         /* Acquire rundown protection */
151         if (ExfAcquireRundownProtectionEx(&CallbackBlock->RundownProtect,
152                                           MAX_FAST_REFS))
153         {
154             /* Insert references */
155             if (!ExInsertFastReference(&CallBack->RoutineBlock, CallbackBlock))
156             {
157                 /* Backdown the rundown acquire */
158                 ExfReleaseRundownProtectionEx(&CallbackBlock->RundownProtect,
159                                               MAX_FAST_REFS);
160             }
161         }
162     }
163 
164     /* Return the callback block */
165     return CallbackBlock;
166 }
167 
168 BOOLEAN
169 NTAPI
170 ExCompareExchangeCallBack(IN OUT PEX_CALLBACK CallBack,
171                           IN PEX_CALLBACK_ROUTINE_BLOCK NewBlock,
172                           IN PEX_CALLBACK_ROUTINE_BLOCK OldBlock)
173 {
174     EX_FAST_REF OldValue;
175     PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock;
176     ULONG Count;
177 
178     /* Check that we have a new block */
179     if (NewBlock)
180     {
181         /* Acquire rundown */
182         if (!ExfAcquireRundownProtectionEx(&NewBlock->RundownProtect,
183                                            MAX_FAST_REFS + 1))
184         {
185             /* This should never happen */
186             ASSERTMSG("Callback block is already undergoing rundown\n", FALSE);
187             return FALSE;
188         }
189     }
190 
191     /* Do the swap */
192     OldValue = ExCompareSwapFastReference(&CallBack->RoutineBlock,
193                                           NewBlock,
194                                           OldBlock);
195 
196     /* Get the routine block */
197     CallbackBlock = ExGetObjectFastReference(OldValue);
198     Count = ExGetCountFastReference(OldValue);
199 
200     /* Make sure the swap worked */
201     if (CallbackBlock == OldBlock)
202     {
203         /* Make sure we replaced a valid pointer */
204         if (CallbackBlock)
205         {
206             /* Acquire the flush lock and immediately release it */
207             KeEnterCriticalRegion();
208             ExWaitOnPushLock(&ExpCallBackFlush);
209 
210             /* Release rundown protection */
211             KeLeaveCriticalRegion();
212             ExfReleaseRundownProtectionEx(&CallbackBlock->RundownProtect,
213                                           Count + 1);
214         }
215 
216         /* Compare worked */
217         return TRUE;
218     }
219     else
220     {
221         /* It failed, check if we had a block */
222         if (NewBlock)
223         {
224             /* We did, remove the references that we had added */
225             ExfReleaseRundownProtectionEx(&NewBlock->RundownProtect,
226                                           MAX_FAST_REFS + 1);
227         }
228 
229         /* Return failure */
230         return FALSE;
231     }
232 }
233 
234 VOID
235 NTAPI
236 ExpDeleteCallback(IN PVOID Object)
237 {
238     /* Sanity check */
239     ASSERT(IsListEmpty(&((PCALLBACK_OBJECT)Object)->RegisteredCallbacks));
240 }
241 
242 /*++
243  * @name ExpInitializeCallbacks
244  *
245  * Creates the Callback Object as a valid Object Type in the Kernel.
246  * Internal function, subject to further review
247  *
248  * @return TRUE if the Callback Object Type was successfully created.
249  *
250  * @remarks None
251  *
252  *--*/
253 CODE_SEG("INIT")
254 BOOLEAN
255 NTAPI
256 ExpInitializeCallbacks(VOID)
257 {
258     OBJECT_ATTRIBUTES ObjectAttributes;
259     NTSTATUS Status;
260     UNICODE_STRING DirName = RTL_CONSTANT_STRING(L"\\Callback");
261     UNICODE_STRING CallbackName;
262     UNICODE_STRING Name;
263     OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
264     HANDLE DirectoryHandle;
265     ULONG i;
266 
267     /* Setup lightweight callback lock */
268     ExpCallBackFlush.Value = 0;
269 
270     /* Initialize the Callback Object type  */
271     RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
272     RtlInitUnicodeString(&Name, L"Callback");
273     ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
274     ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
275     ObjectTypeInitializer.GenericMapping = ExpCallbackMapping;
276     ObjectTypeInitializer.PoolType = NonPagedPool;
277     ObjectTypeInitializer.DeleteProcedure = ExpDeleteCallback;
278     ObjectTypeInitializer.ValidAccessMask = CALLBACK_ALL_ACCESS;
279     Status = ObCreateObjectType(&Name,
280                                 &ObjectTypeInitializer,
281                                 NULL,
282                                 &ExCallbackObjectType);
283     if (!NT_SUCCESS(Status)) return FALSE;
284 
285     /* Initialize the Object */
286     InitializeObjectAttributes(&ObjectAttributes,
287                                &DirName,
288                                OBJ_CASE_INSENSITIVE | OBJ_PERMANENT,
289                                NULL,
290                                SePublicDefaultSd);
291 
292     /* Create the Object Directory */
293     Status = NtCreateDirectoryObject(&DirectoryHandle,
294                                      DIRECTORY_ALL_ACCESS,
295                                      &ObjectAttributes);
296     if (!NT_SUCCESS(Status)) return FALSE;
297 
298     /* Close Handle... */
299     NtClose(DirectoryHandle);
300 
301     /* Initialize Event used when unregistering */
302     KeInitializeEvent(&ExpCallbackEvent, NotificationEvent, 0);
303 
304     /* Default NT Kernel Callbacks. */
305     for (i = 0; ExpInitializeCallback[i].CallbackObject; i++)
306     {
307         /* Create the name from the structure */
308         RtlInitUnicodeString(&CallbackName, ExpInitializeCallback[i].Name);
309 
310         /* Initialize the Object Attributes Structure */
311         InitializeObjectAttributes(&ObjectAttributes,
312                                    &CallbackName,
313                                    OBJ_PERMANENT | OBJ_CASE_INSENSITIVE,
314                                    NULL,
315                                    NULL);
316 
317         /* Create the Callback Object */
318         Status = ExCreateCallback(ExpInitializeCallback[i].CallbackObject,
319                                   &ObjectAttributes,
320                                   TRUE,
321                                   TRUE);
322         if (!NT_SUCCESS(Status)) return FALSE;
323     }
324 
325     /* Everything successful */
326     return TRUE;
327 }
328 
329 /* PUBLIC FUNCTIONS **********************************************************/
330 
331 /*++
332  * @name ExCreateCallback
333  * @implemented
334  *
335  * Opens or creates a Callback Object. Creates only if Create is true.
336  * Allows multiple Callback Functions to be registered only if
337  * AllowMultipleCallbacks is true.
338  * See: http://www.osronline.com/ddkx/kmarch/k102_967m.htm
339  *      http://www.osronline.com/article.cfm?id=24
340  *
341  * @param CallbackObject
342  *        Pointer that will receive the Callback Object.
343  *
344  * @param CallbackName
345  *        Name of Callback
346  *
347  * @param Create
348  *        Determines if the object will be created if it doesn't exit
349  *
350  * @param AllowMultipleCallbacks
351  *        Determines if more then one registered callback function
352  *        can be attached to this Callback Object.
353  *
354  * @return STATUS_SUCESS if not failed.
355  *
356  * @remarks Must be called at IRQL = PASSIVE_LEVEL
357  *
358  *--*/
359 NTSTATUS
360 NTAPI
361 ExCreateCallback(OUT PCALLBACK_OBJECT *CallbackObject,
362                  IN POBJECT_ATTRIBUTES ObjectAttributes,
363                  IN BOOLEAN Create,
364                  IN BOOLEAN AllowMultipleCallbacks)
365 {
366     PCALLBACK_OBJECT Callback = NULL;
367     NTSTATUS Status;
368     HANDLE Handle = NULL;
369     PAGED_CODE();
370 
371     /* Open a handle to the callback if it exists */
372     if (ObjectAttributes->ObjectName)
373     {
374         /* Open the handle */
375         Status = ObOpenObjectByName(ObjectAttributes,
376                                     ExCallbackObjectType,
377                                     KernelMode,
378                                     NULL,
379                                     0,
380                                     NULL,
381                                     &Handle);
382     }
383     else
384     {
385         /* Otherwise, fail */
386         Status = STATUS_UNSUCCESSFUL;
387     }
388 
389     /* We weren't able to open it...should we create it? */
390     if (!(NT_SUCCESS(Status)) && (Create))
391     {
392         /* Create the object */
393         Status = ObCreateObject(KernelMode,
394                                 ExCallbackObjectType,
395                                 ObjectAttributes,
396                                 KernelMode,
397                                 NULL,
398                                 sizeof(CALLBACK_OBJECT),
399                                 0,
400                                 0,
401                                 (PVOID *)&Callback);
402         if (NT_SUCCESS(Status))
403         {
404             /* Set it up */
405             Callback->Signature = 'llaC';
406             KeInitializeSpinLock(&Callback->Lock);
407             InitializeListHead(&Callback->RegisteredCallbacks);
408             Callback->AllowMultipleCallbacks = AllowMultipleCallbacks;
409 
410             /* Insert the object into the object namespace */
411             Status = ObInsertObject(Callback,
412                                     NULL,
413                                     FILE_READ_DATA,
414                                     0,
415                                     NULL,
416                                     &Handle);
417         }
418     }
419 
420     /* Check if we have success until here */
421     if (NT_SUCCESS(Status))
422     {
423         /* Get a pointer to the new object from the handle we just got */
424         Status = ObReferenceObjectByHandle(Handle,
425                                            0,
426                                            ExCallbackObjectType,
427                                            KernelMode,
428                                            (PVOID *)&Callback,
429                                            NULL);
430 
431         /* Close the Handle, since we now have the pointer */
432         ZwClose(Handle);
433     }
434 
435     /* Everything went fine, so return a pointer to the Object */
436     if (NT_SUCCESS(Status))
437     {
438         *CallbackObject = Callback;
439     }
440     return Status;
441 }
442 
443 /*++
444  * @name ExNotifyCallback
445  * @implemented
446  *
447  * Calls a function pointer (a registered callback)
448  * See: http://www.osronline.com/ddkx/kmarch/k102_2f5e.htm
449  *      http://vmsone.com/~decuslib/vmssig/vmslt99b/nt/wdm-callback.txt
450  *
451  * @param CallbackObject
452  *        Which callback to call
453  *
454  * @param Argument1
455  *        Pointer/data to send to callback function
456  *
457  * @param Argument2
458  *        Pointer/data to send to callback function
459  *
460  * @return None
461  *
462  * @remarks None
463  *
464  *--*/
465 VOID
466 NTAPI
467 ExNotifyCallback(IN PCALLBACK_OBJECT CallbackObject,
468                  IN PVOID Argument1,
469                  IN PVOID Argument2)
470 {
471     PLIST_ENTRY RegisteredCallbacks;
472     PCALLBACK_REGISTRATION CallbackRegistration;
473     KIRQL OldIrql;
474 
475     /* Check if we don't have an object or registrations */
476     if (!(CallbackObject) ||
477         (IsListEmpty(&CallbackObject->RegisteredCallbacks)))
478     {
479         /* Don't notify */
480         return;
481     }
482 
483     /* Acquire the Lock */
484     KeAcquireSpinLock(&CallbackObject->Lock, &OldIrql);
485 
486     /* Enumerate through all the registered functions */
487     for (RegisteredCallbacks = CallbackObject->RegisteredCallbacks.Flink;
488          RegisteredCallbacks != &CallbackObject->RegisteredCallbacks;
489          RegisteredCallbacks = RegisteredCallbacks->Flink)
490     {
491         /* Get a pointer to a Callback Registration from the List Entries */
492         CallbackRegistration = CONTAINING_RECORD(RegisteredCallbacks,
493                                                  CALLBACK_REGISTRATION,
494                                                  Link);
495 
496         /* Don't bother doing notification if it's pending to be deleted */
497         if (!CallbackRegistration->UnregisterWaiting)
498         {
499             /* Mark the Callback in use, so it won't get deleted */
500             CallbackRegistration->Busy += 1;
501 
502             /* Release the Spinlock before making the call */
503             KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
504 
505             /* Call the Registered Function */
506             CallbackRegistration->CallbackFunction(CallbackRegistration->
507                                                    CallbackContext,
508                                                    Argument1,
509                                                    Argument2);
510 
511             /* Get SpinLock back */
512             KeAcquireSpinLock(&CallbackObject->Lock, &OldIrql);
513 
514             /* We are not in use anymore */
515             CallbackRegistration->Busy -= 1;
516 
517             /* Check if removal is pending and we're not active */
518             if ((CallbackRegistration->UnregisterWaiting) &&
519                 !(CallbackRegistration->Busy))
520             {
521                 /* Signal the callback event */
522                 KeSetEvent(&ExpCallbackEvent, 0, FALSE);
523             }
524         }
525     }
526 
527     /* Release the Callback Object */
528     KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
529 }
530 
531 /*++
532  * @name ExRegisterCallback
533  * @implemented
534  *
535  * Allows a function to associate a callback pointer (Function) to
536  * a created Callback object
537  * See: DDK, OSR, links in ExNotifyCallback
538  *
539  * @param CallbackObject
540  *        The Object Created with ExCreateCallBack
541  *
542  * @param CallBackFunction
543  *        Pointer to the function to be called back
544  *
545  * @param CallBackContext
546  *        Block of memory that can contain user-data which will be
547  *        passed on to the callback
548  *
549  * @return A handle to a Callback Registration Structure (MSDN Documentation)
550  *
551  * @remarks None
552  *
553  *--*/
554 PVOID
555 NTAPI
556 ExRegisterCallback(IN PCALLBACK_OBJECT CallbackObject,
557                    IN PCALLBACK_FUNCTION CallbackFunction,
558                    IN PVOID CallbackContext)
559 {
560     PCALLBACK_REGISTRATION CallbackRegistration = NULL;
561     KIRQL OldIrql;
562 
563     /* Sanity checks */
564     ASSERT(CallbackFunction);
565     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
566 
567     /* Create reference to Callback Object */
568     ObReferenceObject(CallbackObject);
569 
570     /* Allocate memory for the structure */
571     CallbackRegistration = ExAllocatePoolWithTag(NonPagedPool,
572                                                  sizeof(CALLBACK_REGISTRATION),
573                                                  TAG_CALLBACK_REGISTRATION);
574     if (!CallbackRegistration)
575     {
576         /* Dereference and fail */
577         ObDereferenceObject (CallbackObject);
578         return NULL;
579     }
580 
581     /* Create Callback Registration */
582     CallbackRegistration->CallbackObject = CallbackObject;
583     CallbackRegistration->CallbackFunction = CallbackFunction;
584     CallbackRegistration->CallbackContext = CallbackContext;
585     CallbackRegistration->Busy = 0;
586     CallbackRegistration->UnregisterWaiting = FALSE;
587 
588     /* Acquire SpinLock */
589     KeAcquireSpinLock(&CallbackObject->Lock, &OldIrql);
590 
591     /* Check if 1) No Callbacks registered or 2) Multiple Callbacks allowed */
592     if ((CallbackObject->AllowMultipleCallbacks) ||
593         (IsListEmpty(&CallbackObject->RegisteredCallbacks)))
594     {
595         /* Register the callback */
596         InsertTailList(&CallbackObject->RegisteredCallbacks,
597                        &CallbackRegistration->Link);
598 
599         /* Release SpinLock */
600         KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
601     }
602     else
603     {
604         /* Release SpinLock */
605         KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
606 
607         /* Free the registration */
608         ExFreePoolWithTag(CallbackRegistration, TAG_CALLBACK_REGISTRATION);
609         CallbackRegistration = NULL;
610 
611         /* Dereference the object */
612         ObDereferenceObject(CallbackObject);
613     }
614 
615     /* Return handle to Registration Object */
616     return (PVOID)CallbackRegistration;
617 }
618 
619 /*++
620  * @name ExUnregisterCallback
621  * @implemented
622  *
623  * Deregisters a CallBack
624  * See: DDK, OSR, links in ExNotifyCallback
625  *
626  * @param CallbackRegistration
627  *        Callback Registration Handle
628  *
629  * @return None
630  *
631  * @remarks None
632  *
633  *--*/
634 VOID
635 NTAPI
636 ExUnregisterCallback(IN PVOID CallbackRegistrationHandle)
637 {
638     PCALLBACK_REGISTRATION CallbackRegistration;
639     PCALLBACK_OBJECT CallbackObject;
640     KIRQL OldIrql;
641     ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
642 
643     /* Convert Handle to valid Structure Pointer */
644     CallbackRegistration = (PCALLBACK_REGISTRATION)CallbackRegistrationHandle;
645 
646     /* Get the Callback Object */
647     CallbackObject = CallbackRegistration->CallbackObject;
648 
649     /* Lock the Object */
650     KeAcquireSpinLock (&CallbackObject->Lock, &OldIrql);
651 
652     /* We can't Delete the Callback if it's in use */
653     while (CallbackRegistration->Busy)
654     {
655         /* Let everyone else know we're unregistering */
656         CallbackRegistration->UnregisterWaiting = TRUE;
657 
658         /* We are going to wait for the event, so the Lock isn't necessary */
659         KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
660 
661         /* Make sure the event is cleared */
662         KeClearEvent(&ExpCallbackEvent);
663 
664         /* Wait for the Event */
665         KeWaitForSingleObject(&ExpCallbackEvent,
666                               Executive,
667                               KernelMode,
668                               FALSE,
669                               NULL);
670 
671         /* We need the Lock again */
672         KeAcquireSpinLock(&CallbackObject->Lock, &OldIrql);
673     }
674 
675     /* Remove the Callback */
676     RemoveEntryList(&CallbackRegistration->Link);
677 
678     /* It's now safe to release the lock */
679     KeReleaseSpinLock(&CallbackObject->Lock, OldIrql);
680 
681     /* Delete this registration */
682     ExFreePoolWithTag(CallbackRegistration, TAG_CALLBACK_REGISTRATION);
683 
684     /* Remove the reference */
685     ObDereferenceObject(CallbackObject);
686 }
687 
688 /* EOF */
689