xref: /reactos/ntoskrnl/ex/keyedevt.c (revision c8d07514)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Kernel
4  * FILE:            ntoskrnl/ex/keyedevt.c
5  * PURPOSE:         Support for keyed events
6  * PROGRAMMERS:     Timo Kreuzer (timo.kreuzer@reactos.org)
7  */
8 
9 /* INCLUDES *****************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 /* INTERNAL TYPES *************************************************************/
16 
17 #define NUM_KEY_HASH_BUCKETS 23
18 typedef struct _EX_KEYED_EVENT
19 {
20     struct
21     {
22         EX_PUSH_LOCK Lock;
23         LIST_ENTRY WaitListHead;
24         LIST_ENTRY ReleaseListHead;
25     } HashTable[NUM_KEY_HASH_BUCKETS];
26 } EX_KEYED_EVENT, *PEX_KEYED_EVENT;
27 
28 /* GLOBALS *******************************************************************/
29 
30 PEX_KEYED_EVENT ExpCritSecOutOfMemoryEvent;
31 POBJECT_TYPE ExKeyedEventObjectType;
32 
33 static
34 GENERIC_MAPPING ExpKeyedEventMapping =
35 {
36     STANDARD_RIGHTS_READ | KEYEDEVENT_WAIT,
37     STANDARD_RIGHTS_WRITE | KEYEDEVENT_WAKE,
38     STANDARD_RIGHTS_EXECUTE,
39     KEYEDEVENT_ALL_ACCESS
40 };
41 
42 /* FUNCTIONS *****************************************************************/
43 
44 _IRQL_requires_max_(APC_LEVEL)
45 CODE_SEG("INIT")
46 BOOLEAN
47 NTAPI
48 ExpInitializeKeyedEventImplementation(VOID)
49 {
50     OBJECT_TYPE_INITIALIZER ObjectTypeInitializer = {0};
51     UNICODE_STRING TypeName = RTL_CONSTANT_STRING(L"KeyedEvent");
52     UNICODE_STRING Name = RTL_CONSTANT_STRING(L"\\KernelObjects\\CritSecOutOfMemoryEvent");
53     NTSTATUS Status;
54     HANDLE EventHandle;
55     OBJECT_ATTRIBUTES ObjectAttributes;
56 
57     /* Set up the object type initializer */
58     ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
59     ObjectTypeInitializer.GenericMapping = ExpKeyedEventMapping;
60     ObjectTypeInitializer.PoolType = PagedPool;
61     ObjectTypeInitializer.ValidAccessMask = KEYEDEVENT_ALL_ACCESS;
62     ObjectTypeInitializer.UseDefaultObject = TRUE;
63 
64     /* Create the keyed event object type */
65     Status = ObCreateObjectType(&TypeName,
66                                 &ObjectTypeInitializer,
67                                 NULL,
68                                 &ExKeyedEventObjectType);
69     if (!NT_SUCCESS(Status)) return FALSE;
70 
71     /* Create the out of memory event for critical sections */
72     InitializeObjectAttributes(&ObjectAttributes, &Name, OBJ_PERMANENT, NULL, NULL);
73     Status = ZwCreateKeyedEvent(&EventHandle,
74                                 KEYEDEVENT_ALL_ACCESS,
75                                 &ObjectAttributes,
76                                 0);
77     if (NT_SUCCESS(Status))
78     {
79         /* Take a reference so we can get rid of the handle */
80         Status = ObReferenceObjectByHandle(EventHandle,
81                                            KEYEDEVENT_ALL_ACCESS,
82                                            ExKeyedEventObjectType,
83                                            KernelMode,
84                                            (PVOID*)&ExpCritSecOutOfMemoryEvent,
85                                            NULL);
86         ZwClose(EventHandle);
87         return TRUE;
88     }
89 
90     return FALSE;
91 }
92 
93 VOID
94 NTAPI
95 ExpInitializeKeyedEvent(
96     _Out_ PEX_KEYED_EVENT KeyedEvent)
97 {
98     ULONG i;
99 
100     /* Loop all hash buckets */
101     for (i = 0; i < NUM_KEY_HASH_BUCKETS; i++)
102     {
103         /* Initialize the mutex and the wait lists */
104         ExInitializePushLock(&KeyedEvent->HashTable[i].Lock);
105         InitializeListHead(&KeyedEvent->HashTable[i].WaitListHead);
106         InitializeListHead(&KeyedEvent->HashTable[i].ReleaseListHead);
107     }
108 }
109 
110 _IRQL_requires_max_(APC_LEVEL)
111 NTSTATUS
112 NTAPI
113 ExpReleaseOrWaitForKeyedEvent(
114     _Inout_ PEX_KEYED_EVENT KeyedEvent,
115     _In_ PVOID KeyedWaitValue,
116     _In_ BOOLEAN Alertable,
117     _In_ PLARGE_INTEGER Timeout,
118     _In_ BOOLEAN Release)
119 {
120     PETHREAD Thread, CurrentThread;
121     PEPROCESS CurrentProcess;
122     PLIST_ENTRY ListEntry, WaitListHead1, WaitListHead2;
123     NTSTATUS Status;
124     ULONG_PTR HashIndex;
125     PVOID PreviousKeyedWaitValue;
126 
127     /* Get the current process */
128     CurrentProcess = PsGetCurrentProcess();
129 
130     /* Calculate the hash index */
131     HashIndex = (ULONG_PTR)KeyedWaitValue >> 5;
132     HashIndex ^= (ULONG_PTR)CurrentProcess >> 6;
133     HashIndex %= NUM_KEY_HASH_BUCKETS;
134 
135     /* Lock the lists */
136     KeEnterCriticalRegion();
137     ExAcquirePushLockExclusive(&KeyedEvent->HashTable[HashIndex].Lock);
138 
139     /* Get the lists for search and wait, depending on whether
140        we want to wait for the event or signal it */
141     if (Release)
142     {
143         WaitListHead1 = &KeyedEvent->HashTable[HashIndex].WaitListHead;
144         WaitListHead2 = &KeyedEvent->HashTable[HashIndex].ReleaseListHead;
145     }
146     else
147     {
148         WaitListHead1 = &KeyedEvent->HashTable[HashIndex].ReleaseListHead;
149         WaitListHead2 = &KeyedEvent->HashTable[HashIndex].WaitListHead;
150     }
151 
152     /* loop the first wait list */
153     ListEntry = WaitListHead1->Flink;
154     while (ListEntry != WaitListHead1)
155     {
156         /* Get the waiting thread. Note that this thread cannot be terminated
157            as long as we hold the list lock, since it either needs to wait to
158            be signaled by this thread or, when the wait is aborted due to thread
159            termination, then it first needs to acquire the list lock. */
160         Thread = CONTAINING_RECORD(ListEntry, ETHREAD, KeyedWaitChain);
161         ListEntry = ListEntry->Flink;
162 
163         /* Check if this thread is a correct waiter */
164         if ((Thread->Tcb.Process == &CurrentProcess->Pcb) &&
165             (Thread->KeyedWaitValue == KeyedWaitValue))
166         {
167             /* Remove the thread from the list */
168             RemoveEntryList(&Thread->KeyedWaitChain);
169 
170             /* Initialize the list entry to show that it was removed */
171             InitializeListHead(&Thread->KeyedWaitChain);
172 
173             /* Wake the thread */
174             KeReleaseSemaphore(&Thread->KeyedWaitSemaphore,
175                                IO_NO_INCREMENT,
176                                1,
177                                FALSE);
178             Thread = NULL;
179 
180             /* Unlock the list. After this it is not safe to access Thread */
181             ExReleasePushLockExclusive(&KeyedEvent->HashTable[HashIndex].Lock);
182             KeLeaveCriticalRegion();
183 
184             return STATUS_SUCCESS;
185         }
186     }
187 
188     /* Get the current thread */
189     CurrentThread = PsGetCurrentThread();
190 
191     /* Set the wait key and remember the old value */
192     PreviousKeyedWaitValue = CurrentThread->KeyedWaitValue;
193     CurrentThread->KeyedWaitValue = KeyedWaitValue;
194 
195     /* Initialize the wait semaphore */
196     KeInitializeSemaphore(&CurrentThread->KeyedWaitSemaphore, 0, 1);
197 
198     /* Insert the current thread into the secondary wait list */
199     InsertTailList(WaitListHead2, &CurrentThread->KeyedWaitChain);
200 
201     /* Unlock the list */
202     ExReleasePushLockExclusive(&KeyedEvent->HashTable[HashIndex].Lock);
203     KeLeaveCriticalRegion();
204 
205     /* Wait for the keyed wait semaphore */
206     Status = KeWaitForSingleObject(&CurrentThread->KeyedWaitSemaphore,
207                                    WrKeyedEvent,
208                                    KernelMode,
209                                    Alertable,
210                                    Timeout);
211 
212     /* Check if the wait was aborted or timed out */
213     if (Status != STATUS_SUCCESS)
214     {
215         /* Lock the lists to make sure no one else messes with the entry */
216         KeEnterCriticalRegion();
217         ExAcquirePushLockExclusive(&KeyedEvent->HashTable[HashIndex].Lock);
218 
219         /* Check if the wait list entry is still in the list */
220         if (!IsListEmpty(&CurrentThread->KeyedWaitChain))
221         {
222             /* Remove the thread from the list */
223             RemoveEntryList(&CurrentThread->KeyedWaitChain);
224             InitializeListHead(&CurrentThread->KeyedWaitChain);
225         }
226 
227         /* Unlock the list */
228         ExReleasePushLockExclusive(&KeyedEvent->HashTable[HashIndex].Lock);
229         KeLeaveCriticalRegion();
230     }
231 
232     /* Restore the previous KeyedWaitValue, since this is a union member */
233     CurrentThread->KeyedWaitValue = PreviousKeyedWaitValue;
234 
235     return Status;
236 }
237 
238 _IRQL_requires_max_(APC_LEVEL)
239 NTSTATUS
240 NTAPI
241 ExpWaitForKeyedEvent(
242     _Inout_ PEX_KEYED_EVENT KeyedEvent,
243     _In_ PVOID KeyedWaitValue,
244     _In_ BOOLEAN Alertable,
245     _In_ PLARGE_INTEGER Timeout)
246 {
247     /* Call the generic internal function */
248     return ExpReleaseOrWaitForKeyedEvent(KeyedEvent,
249                                          KeyedWaitValue,
250                                          Alertable,
251                                          Timeout,
252                                          FALSE);
253 }
254 
255 _IRQL_requires_max_(APC_LEVEL)
256 NTSTATUS
257 NTAPI
258 ExpReleaseKeyedEvent(
259     _Inout_ PEX_KEYED_EVENT KeyedEvent,
260     _In_ PVOID KeyedWaitValue,
261     _In_ BOOLEAN Alertable,
262     _In_ PLARGE_INTEGER Timeout)
263 {
264     /* Call the generic internal function */
265     return ExpReleaseOrWaitForKeyedEvent(KeyedEvent,
266                                          KeyedWaitValue,
267                                          Alertable,
268                                          Timeout,
269                                          TRUE);
270 }
271 
272 _IRQL_requires_max_(PASSIVE_LEVEL)
273 NTSTATUS
274 NTAPI
275 NtCreateKeyedEvent(
276     _Out_ PHANDLE OutHandle,
277     _In_ ACCESS_MASK AccessMask,
278     _In_ POBJECT_ATTRIBUTES ObjectAttributes,
279     _In_ ULONG Flags)
280 {
281     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
282     PEX_KEYED_EVENT KeyedEvent;
283     HANDLE KeyedEventHandle;
284     NTSTATUS Status;
285 
286     /* Check flags */
287     if (Flags != 0)
288     {
289         /* We don't support any flags yet */
290         return STATUS_INVALID_PARAMETER;
291     }
292 
293     /* Create the object */
294     Status = ObCreateObject(PreviousMode,
295                             ExKeyedEventObjectType,
296                             ObjectAttributes,
297                             PreviousMode,
298                             NULL,
299                             sizeof(EX_KEYED_EVENT),
300                             0,
301                             0,
302                             (PVOID*)&KeyedEvent);
303 
304     /* Check for success */
305     if (!NT_SUCCESS(Status)) return Status;
306 
307     /* Initialize the keyed event */
308     ExpInitializeKeyedEvent(KeyedEvent);
309 
310     /* Insert it */
311     Status = ObInsertObject(KeyedEvent,
312                             NULL,
313                             AccessMask,
314                             0,
315                             NULL,
316                             &KeyedEventHandle);
317 
318     /* Check for success (ObInsertObject dereferences!) */
319     if (!NT_SUCCESS(Status)) return Status;
320 
321     if (PreviousMode != KernelMode)
322     {
323         /* Enter SEH for return */
324         _SEH2_TRY
325         {
326             /* Return the handle to the caller */
327             ProbeForWrite(OutHandle, sizeof(HANDLE), sizeof(HANDLE));
328             *OutHandle = KeyedEventHandle;
329         }
330         _SEH2_EXCEPT(ExSystemExceptionFilter())
331         {
332             /* Get the exception code */
333             Status = _SEH2_GetExceptionCode();
334 
335             /* Cleanup */
336             ObCloseHandle(KeyedEventHandle, PreviousMode);
337         }
338         _SEH2_END;
339     }
340     else
341     {
342         *OutHandle = KeyedEventHandle;
343     }
344 
345     /* Return Status */
346     return Status;
347 }
348 
349 _IRQL_requires_max_(PASSIVE_LEVEL)
350 NTSTATUS
351 NTAPI
352 NtOpenKeyedEvent(
353     _Out_ PHANDLE OutHandle,
354     _In_ ACCESS_MASK AccessMask,
355     _In_ POBJECT_ATTRIBUTES ObjectAttributes)
356 {
357     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
358     HANDLE KeyedEventHandle;
359     NTSTATUS Status;
360 
361     /* Open the object */
362     Status = ObOpenObjectByName(ObjectAttributes,
363                                 ExKeyedEventObjectType,
364                                 PreviousMode,
365                                 NULL,
366                                 AccessMask,
367                                 NULL,
368                                 &KeyedEventHandle);
369 
370     /* Check for success */
371     if (!NT_SUCCESS(Status)) return Status;
372 
373     /* Enter SEH for return */
374     if (PreviousMode != KernelMode)
375     {
376         _SEH2_TRY
377         {
378             /* Return the handle to the caller */
379             ProbeForWrite(OutHandle, sizeof(HANDLE), sizeof(HANDLE));
380             *OutHandle = KeyedEventHandle;
381         }
382         _SEH2_EXCEPT(ExSystemExceptionFilter())
383         {
384             /* Get the exception code */
385             Status = _SEH2_GetExceptionCode();
386 
387             /* Cleanup */
388             ObCloseHandle(KeyedEventHandle, PreviousMode);
389         }
390         _SEH2_END;
391     }
392     else
393     {
394         *OutHandle = KeyedEventHandle;
395     }
396 
397     /* Return status */
398     return Status;
399 }
400 
401 _IRQL_requires_max_(PASSIVE_LEVEL)
402 NTSTATUS
403 NTAPI
404 NtWaitForKeyedEvent(
405     _In_opt_ HANDLE Handle,
406     _In_ PVOID Key,
407     _In_ BOOLEAN Alertable,
408     _In_opt_ PLARGE_INTEGER Timeout)
409 {
410     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
411     PEX_KEYED_EVENT KeyedEvent;
412     NTSTATUS Status;
413     LARGE_INTEGER TimeoutCopy;
414 
415     /* Key must always be two-byte aligned */
416     if ((ULONG_PTR)Key & 1)
417     {
418         return STATUS_INVALID_PARAMETER_1;
419     }
420 
421     /* Check if the caller passed a timeout value and this is from user mode */
422     if ((Timeout != NULL) && (PreviousMode != KernelMode))
423     {
424         _SEH2_TRY
425         {
426             ProbeForRead(Timeout, sizeof(*Timeout), 1);
427             TimeoutCopy = *Timeout;
428             Timeout = &TimeoutCopy;
429         }
430         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
431         {
432             _SEH2_YIELD(return _SEH2_GetExceptionCode());
433         }
434         _SEH2_END;
435     }
436 
437     /* Check if the caller provided a handle */
438     if (Handle != NULL)
439     {
440         /* Get the keyed event object */
441         Status = ObReferenceObjectByHandle(Handle,
442                                            KEYEDEVENT_WAIT,
443                                            ExKeyedEventObjectType,
444                                            PreviousMode,
445                                            (PVOID*)&KeyedEvent,
446                                            NULL);
447 
448         /* Check for success */
449         if (!NT_SUCCESS(Status)) return Status;
450     }
451     else
452     {
453         /* Use the default keyed event for low memory critical sections */
454         KeyedEvent = ExpCritSecOutOfMemoryEvent;
455     }
456 
457     /* Do the wait */
458     Status = ExpWaitForKeyedEvent(KeyedEvent, Key, Alertable, Timeout);
459 
460     if (Handle != NULL)
461     {
462         /* Dereference the keyed event */
463         ObDereferenceObject(KeyedEvent);
464     }
465 
466     /* Return the status */
467     return Status;
468 }
469 
470 _IRQL_requires_max_(PASSIVE_LEVEL)
471 NTSTATUS
472 NTAPI
473 NtReleaseKeyedEvent(
474     _In_opt_ HANDLE Handle,
475     _In_ PVOID Key,
476     _In_ BOOLEAN Alertable,
477     _In_opt_ PLARGE_INTEGER Timeout)
478 {
479     KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
480     PEX_KEYED_EVENT KeyedEvent;
481     NTSTATUS Status;
482     LARGE_INTEGER TimeoutCopy;
483 
484     /* Key must always be two-byte aligned */
485     if ((ULONG_PTR)Key & 1)
486     {
487         return STATUS_INVALID_PARAMETER_1;
488     }
489 
490     /* Check if the caller passed a timeout value and this is from user mode */
491     if ((Timeout != NULL) && (PreviousMode != KernelMode))
492     {
493         _SEH2_TRY
494         {
495             ProbeForRead(Timeout, sizeof(*Timeout), 1);
496             TimeoutCopy = *Timeout;
497             Timeout = &TimeoutCopy;
498         }
499         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
500         {
501             _SEH2_YIELD(return _SEH2_GetExceptionCode());
502         }
503         _SEH2_END;
504     }
505 
506     /* Check if the caller provided a handle */
507     if (Handle != NULL)
508     {
509         /* Get the keyed event object */
510         Status = ObReferenceObjectByHandle(Handle,
511                                            KEYEDEVENT_WAKE,
512                                            ExKeyedEventObjectType,
513                                            PreviousMode,
514                                            (PVOID*)&KeyedEvent,
515                                            NULL);
516 
517         /* Check for success */
518         if (!NT_SUCCESS(Status)) return Status;
519     }
520     else
521     {
522         /* Use the default keyed event for low memory critical sections */
523         KeyedEvent = ExpCritSecOutOfMemoryEvent;
524     }
525 
526     /* Do the wait */
527     Status = ExpReleaseKeyedEvent(KeyedEvent, Key, Alertable, Timeout);
528 
529     if (Handle != NULL)
530     {
531         /* Dereference the keyed event */
532         ObDereferenceObject(KeyedEvent);
533     }
534 
535     /* Return the status */
536     return Status;
537 }
538 
539 /* EOF */
540