Synchronize with trunk r58457.
[reactos.git] / ntoskrnl / ex / callback.c
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", 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 refernces 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 BOOLEAN
254 INIT_FUNCTION
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 registred 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 */