ff8cb3f6459c3b7b59b5f87c7047868b6228bb26
[reactos.git] / reactos / ntoskrnl / mm / ARM3 / session.c
1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: ntoskrnl/mm/ARM3/session.c
5 * PURPOSE: Session support routines
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 * Timo Kreuzer (timo.kreuzer@reactos.org)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15
16 #define MODULE_INVOLVED_IN_ARM3
17 #include <mm/ARM3/miarm.h>
18
19 /* GLOBALS ********************************************************************/
20
21 PMM_SESSION_SPACE MmSessionSpace;
22 PFN_NUMBER MiSessionDataPages, MiSessionTagPages, MiSessionTagSizePages;
23 PFN_NUMBER MiSessionBigPoolPages, MiSessionCreateCharge;
24 KGUARDED_MUTEX MiSessionIdMutex;
25 LONG MmSessionDataPages;
26 PRTL_BITMAP MiSessionIdBitmap;
27 volatile LONG MiSessionLeaderExists;
28
29 LIST_ENTRY MiSessionWsList;
30 LIST_ENTRY MmWorkingSetExpansionHead;
31
32 KSPIN_LOCK MmExpansionLock;
33 PETHREAD MiExpansionLockOwner;
34
35
36 /* PRIVATE FUNCTIONS **********************************************************/
37
38 FORCEINLINE
39 KIRQL
40 MiAcquireExpansionLock()
41 {
42 KIRQL OldIrql;
43
44 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
45 KeAcquireSpinLock(&MmExpansionLock, &OldIrql);
46 ASSERT(MiExpansionLockOwner == NULL);
47 MiExpansionLockOwner = PsGetCurrentThread();
48 return OldIrql;
49 }
50
51 FORCEINLINE
52 VOID
53 MiReleaseExpansionLock(KIRQL OldIrql)
54 {
55 ASSERT(MiExpansionLockOwner == PsGetCurrentThread());
56 MiExpansionLockOwner = NULL;
57 KeReleaseSpinLock(&MmExpansionLock, OldIrql);
58 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
59 }
60
61 VOID
62 NTAPI
63 MiInitializeSessionWsSupport(VOID)
64 {
65 /* Initialize the list heads */
66 InitializeListHead(&MiSessionWsList);
67 InitializeListHead(&MmWorkingSetExpansionHead);
68 }
69
70 LCID
71 NTAPI
72 MmGetSessionLocaleId(VOID)
73 {
74 PEPROCESS Process;
75 PAGED_CODE();
76
77 //
78 // Get the current process
79 //
80 Process = PsGetCurrentProcess();
81
82 //
83 // Check if it's NOT the Session Leader
84 //
85 if (!Process->Vm.Flags.SessionLeader)
86 {
87 //
88 // Make sure it has a valid Session
89 //
90 if (Process->Session)
91 {
92 //
93 // Get the Locale ID
94 //
95 return ((PMM_SESSION_SPACE)Process->Session)->LocaleId;
96 }
97 }
98
99 //
100 // Not a session leader, return the default
101 //
102 return PsDefaultThreadLocaleId;
103 }
104
105 _IRQL_requires_max_(APC_LEVEL)
106 VOID
107 NTAPI
108 MmSetSessionLocaleId(
109 _In_ LCID LocaleId)
110 {
111 PEPROCESS CurrentProcess;
112 PAGED_CODE();
113
114 /* Get the current process and check if it is in a session */
115 CurrentProcess = PsGetCurrentProcess();
116 if ((CurrentProcess->Vm.Flags.SessionLeader == 0) &&
117 (CurrentProcess->Session != NULL))
118 {
119 /* Set the session locale Id */
120 ((PMM_SESSION_SPACE)CurrentProcess->Session)->LocaleId = LocaleId;
121 }
122 else
123 {
124 /* Set the default locale */
125 PsDefaultThreadLocaleId = LocaleId;
126 }
127 }
128
129
130 VOID
131 NTAPI
132 MiInitializeSessionIds(VOID)
133 {
134 ULONG Size, BitmapSize;
135 PFN_NUMBER TotalPages;
136
137 /* Setup the total number of data pages needed for the structure */
138 TotalPages = MI_SESSION_DATA_PAGES_MAXIMUM;
139 MiSessionDataPages = ROUND_TO_PAGES(sizeof(MM_SESSION_SPACE)) >> PAGE_SHIFT;
140 ASSERT(MiSessionDataPages <= MI_SESSION_DATA_PAGES_MAXIMUM - 3);
141 TotalPages -= MiSessionDataPages;
142
143 /* Setup the number of pages needed for session pool tags */
144 MiSessionTagSizePages = 2;
145 MiSessionBigPoolPages = 1;
146 MiSessionTagPages = MiSessionTagSizePages + MiSessionBigPoolPages;
147 ASSERT(MiSessionTagPages <= TotalPages);
148 ASSERT(MiSessionTagPages < MI_SESSION_TAG_PAGES_MAXIMUM);
149
150 /* Total pages needed for a session (FIXME: Probably different on PAE/x64) */
151 MiSessionCreateCharge = 1 + MiSessionDataPages + MiSessionTagPages;
152
153 /* Initialize the lock */
154 KeInitializeGuardedMutex(&MiSessionIdMutex);
155
156 /* Allocate the bitmap */
157 Size = MI_INITIAL_SESSION_IDS;
158 BitmapSize = ((Size + 31) / 32) * sizeof(ULONG);
159 MiSessionIdBitmap = ExAllocatePoolWithTag(PagedPool,
160 sizeof(RTL_BITMAP) + BitmapSize,
161 ' mM');
162 if (MiSessionIdBitmap)
163 {
164 /* Free all the bits */
165 RtlInitializeBitMap(MiSessionIdBitmap,
166 (PVOID)(MiSessionIdBitmap + 1),
167 Size);
168 RtlClearAllBits(MiSessionIdBitmap);
169 }
170 else
171 {
172 /* Die if we couldn't allocate the bitmap */
173 KeBugCheckEx(INSTALL_MORE_MEMORY,
174 MmNumberOfPhysicalPages,
175 MmLowestPhysicalPage,
176 MmHighestPhysicalPage,
177 0x200);
178 }
179 }
180
181 VOID
182 NTAPI
183 MiSessionLeader(IN PEPROCESS Process)
184 {
185 KIRQL OldIrql;
186
187 /* Set the flag while under the expansion lock */
188 OldIrql = KeAcquireQueuedSpinLock(LockQueueExpansionLock);
189 Process->Vm.Flags.SessionLeader = TRUE;
190 KeReleaseQueuedSpinLock(LockQueueExpansionLock, OldIrql);
191 }
192
193 ULONG
194 NTAPI
195 MmGetSessionId(IN PEPROCESS Process)
196 {
197 PMM_SESSION_SPACE SessionGlobal;
198
199 /* The session leader is always session zero */
200 if (Process->Vm.Flags.SessionLeader == 1) return 0;
201
202 /* Otherwise, get the session global, and read the session ID from it */
203 SessionGlobal = (PMM_SESSION_SPACE)Process->Session;
204 if (!SessionGlobal) return 0;
205 return SessionGlobal->SessionId;
206 }
207
208 ULONG
209 NTAPI
210 MmGetSessionIdEx(IN PEPROCESS Process)
211 {
212 PMM_SESSION_SPACE SessionGlobal;
213
214 /* The session leader is always session zero */
215 if (Process->Vm.Flags.SessionLeader == 1) return 0;
216
217 /* Otherwise, get the session global, and read the session ID from it */
218 SessionGlobal = (PMM_SESSION_SPACE)Process->Session;
219 if (!SessionGlobal) return -1;
220 return SessionGlobal->SessionId;
221 }
222
223 VOID
224 NTAPI
225 MiReleaseProcessReferenceToSessionDataPage(IN PMM_SESSION_SPACE SessionGlobal)
226 {
227 ULONG i, SessionId;
228 PMMPTE PointerPte;
229 PFN_NUMBER PageFrameIndex[MI_SESSION_DATA_PAGES_MAXIMUM];
230 PMMPFN Pfn1;
231 KIRQL OldIrql;
232
233 /* Is there more than just this reference? If so, bail out */
234 if (InterlockedDecrement(&SessionGlobal->ProcessReferenceToSession)) return;
235
236 /* Get the session ID */
237 SessionId = SessionGlobal->SessionId;
238 DPRINT1("Last process in session %lu going down!!!\n", SessionId);
239
240 /* Free the session page tables */
241 #ifndef _M_AMD64
242 ExFreePoolWithTag(SessionGlobal->PageTables, 'tHmM');
243 #endif
244 ASSERT(!MI_IS_PHYSICAL_ADDRESS(SessionGlobal));
245
246 /* Capture the data page PFNs */
247 PointerPte = MiAddressToPte(SessionGlobal);
248 for (i = 0; i < MiSessionDataPages; i++)
249 {
250 PageFrameIndex[i] = PFN_FROM_PTE(PointerPte + i);
251 }
252
253 /* Release them */
254 MiReleaseSystemPtes(PointerPte, MiSessionDataPages, SystemPteSpace);
255
256 /* Mark them as deleted */
257 for (i = 0; i < MiSessionDataPages; i++)
258 {
259 Pfn1 = MI_PFN_ELEMENT(PageFrameIndex[i]);
260 MI_SET_PFN_DELETED(Pfn1);
261 }
262
263 /* Loop every data page and drop a reference count */
264 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
265 for (i = 0; i < MiSessionDataPages; i++)
266 {
267 /* Sanity check that the page is correct, then decrement it */
268 Pfn1 = MI_PFN_ELEMENT(PageFrameIndex[i]);
269 ASSERT(Pfn1->u2.ShareCount == 1);
270 ASSERT(Pfn1->u3.e2.ReferenceCount == 1);
271 MiDecrementShareCount(Pfn1, PageFrameIndex[i]);
272 }
273
274 /* Done playing with pages, release the lock */
275 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
276
277 /* Decrement the number of data pages */
278 InterlockedDecrement(&MmSessionDataPages);
279
280 /* Free this session ID from the session bitmap */
281 KeAcquireGuardedMutex(&MiSessionIdMutex);
282 ASSERT(RtlCheckBit(MiSessionIdBitmap, SessionId));
283 RtlClearBit(MiSessionIdBitmap, SessionId);
284 KeReleaseGuardedMutex(&MiSessionIdMutex);
285 }
286
287 VOID
288 NTAPI
289 MiDereferenceSessionFinal(VOID)
290 {
291 PMM_SESSION_SPACE SessionGlobal;
292 KIRQL OldIrql;
293
294 /* Get the pointer to the global session address */
295 SessionGlobal = MmSessionSpace->GlobalVirtualAddress;
296
297 /* Acquire the expansion lock */
298 OldIrql = MiAcquireExpansionLock();
299
300 /* Set delete pending flag, so that processes can no longer attach to this
301 session and the last process that detaches sets the AttachEvent */
302 ASSERT(SessionGlobal->u.Flags.DeletePending == 0);
303 SessionGlobal->u.Flags.DeletePending = 1;
304
305 /* Check if we have any attached processes */
306 if (SessionGlobal->AttachCount)
307 {
308 /* Initialize the event (it's not in use yet!) */
309 KeInitializeEvent(&SessionGlobal->AttachEvent, NotificationEvent, FALSE);
310
311 /* Release the expansion lock for the wait */
312 MiReleaseExpansionLock(OldIrql);
313
314 /* Wait for the event to be set due to the last process detach */
315 KeWaitForSingleObject(&SessionGlobal->AttachEvent, WrVirtualMemory, 0, 0, 0);
316
317 /* Reacquire the expansion lock */
318 OldIrql = MiAcquireExpansionLock();
319
320 /* Makes sure we still have the delete flag and no attached processes */
321 ASSERT(MmSessionSpace->u.Flags.DeletePending == 1);
322 ASSERT(MmSessionSpace->AttachCount == 0);
323 }
324
325 /* Check if the session is in the workingset expansion list */
326 if (SessionGlobal->Vm.WorkingSetExpansionLinks.Flink != NULL)
327 {
328 /* Remove the session from the list and zero the list entry */
329 RemoveEntryList(&SessionGlobal->Vm.WorkingSetExpansionLinks);
330 SessionGlobal->Vm.WorkingSetExpansionLinks.Flink = 0;
331 }
332
333 /* Check if the session is in the workingset list */
334 if (SessionGlobal->WsListEntry.Flink)
335 {
336 /* Remove the session from the list and zero the list entry */
337 RemoveEntryList(&SessionGlobal->WsListEntry);
338 SessionGlobal->WsListEntry.Flink = NULL;
339 }
340
341 /* Release the expansion lock */
342 MiReleaseExpansionLock(OldIrql);
343
344 /* Check for a win32k unload routine */
345 if (SessionGlobal->Win32KDriverUnload)
346 {
347 /* Call it */
348 SessionGlobal->Win32KDriverUnload(NULL);
349 }
350 }
351
352
353 VOID
354 NTAPI
355 MiDereferenceSession(VOID)
356 {
357 PMM_SESSION_SPACE SessionGlobal;
358 PEPROCESS Process;
359 ULONG ReferenceCount, SessionId;
360
361 /* Sanity checks */
362 ASSERT(PsGetCurrentProcess()->ProcessInSession ||
363 ((MmSessionSpace->u.Flags.Initialized == 0) &&
364 (PsGetCurrentProcess()->Vm.Flags.SessionLeader == 1) &&
365 (MmSessionSpace->ReferenceCount == 1)));
366
367 /* The session bit must be set */
368 SessionId = MmSessionSpace->SessionId;
369 ASSERT(RtlCheckBit(MiSessionIdBitmap, SessionId));
370
371 /* Get the current process */
372 Process = PsGetCurrentProcess();
373
374 /* Decrement the process count */
375 InterlockedDecrement(&MmSessionSpace->ResidentProcessCount);
376
377 /* Decrement the reference count and check if was the last reference */
378 ReferenceCount = InterlockedDecrement(&MmSessionSpace->ReferenceCount);
379 if (ReferenceCount == 0)
380 {
381 /* No more references left, kill the session completely */
382 MiDereferenceSessionFinal();
383 }
384
385 /* Check if tis is the session leader or the last process in the session */
386 if ((Process->Vm.Flags.SessionLeader) || (ReferenceCount == 0))
387 {
388 /* Get the global session address before we kill the session mapping */
389 SessionGlobal = MmSessionSpace->GlobalVirtualAddress;
390
391 /* Delete all session PDEs and flush the TB */
392 RtlZeroMemory(MiAddressToPde(MmSessionBase),
393 BYTES_TO_PAGES(MmSessionSize) * sizeof(MMPDE));
394 KeFlushEntireTb(FALSE, FALSE);
395
396 /* Is this the session leader? */
397 if (Process->Vm.Flags.SessionLeader)
398 {
399 /* Clean up the references here. */
400 ASSERT(Process->Session == NULL);
401 MiReleaseProcessReferenceToSessionDataPage(SessionGlobal);
402 }
403 }
404
405 /* Reset the current process' session flag */
406 RtlInterlockedClearBits(&Process->Flags, PSF_PROCESS_IN_SESSION_BIT);
407 }
408
409 VOID
410 NTAPI
411 MiSessionRemoveProcess(VOID)
412 {
413 PEPROCESS CurrentProcess = PsGetCurrentProcess();
414 KIRQL OldIrql;
415
416 /* If the process isn't already in a session, or if it's the leader... */
417 if (!(CurrentProcess->Flags & PSF_PROCESS_IN_SESSION_BIT) ||
418 (CurrentProcess->Vm.Flags.SessionLeader))
419 {
420 /* Then there's nothing to do */
421 return;
422 }
423
424 /* Sanity check */
425 ASSERT(MmIsAddressValid(MmSessionSpace) == TRUE);
426
427 /* Acquire the expansion lock while touching the session */
428 OldIrql = MiAcquireExpansionLock();
429
430 /* Remove the process from the list */
431 RemoveEntryList(&CurrentProcess->SessionProcessLinks);
432
433 /* Release the lock again */
434 MiReleaseExpansionLock(OldIrql);
435
436 /* Dereference the session */
437 MiDereferenceSession();
438 }
439
440 VOID
441 NTAPI
442 MiSessionAddProcess(IN PEPROCESS NewProcess)
443 {
444 PMM_SESSION_SPACE SessionGlobal;
445 KIRQL OldIrql;
446
447 /* The current process must already be in a session */
448 if (!(PsGetCurrentProcess()->Flags & PSF_PROCESS_IN_SESSION_BIT)) return;
449
450 /* Sanity check */
451 ASSERT(MmIsAddressValid(MmSessionSpace) == TRUE);
452
453 /* Get the global session */
454 SessionGlobal = MmSessionSpace->GlobalVirtualAddress;
455
456 /* Increment counters */
457 InterlockedIncrement((PLONG)&SessionGlobal->ReferenceCount);
458 InterlockedIncrement(&SessionGlobal->ResidentProcessCount);
459 InterlockedIncrement(&SessionGlobal->ProcessReferenceToSession);
460
461 /* Set the session pointer */
462 ASSERT(NewProcess->Session == NULL);
463 NewProcess->Session = SessionGlobal;
464
465 /* Acquire the expansion lock while touching the session */
466 OldIrql = MiAcquireExpansionLock();
467
468 /* Insert it into the process list */
469 InsertTailList(&SessionGlobal->ProcessList, &NewProcess->SessionProcessLinks);
470
471 /* Release the lock again */
472 MiReleaseExpansionLock(OldIrql);
473
474 /* Set the flag */
475 PspSetProcessFlag(NewProcess, PSF_PROCESS_IN_SESSION_BIT);
476 }
477
478 NTSTATUS
479 NTAPI
480 MiSessionInitializeWorkingSetList(VOID)
481 {
482 KIRQL OldIrql;
483 PMMPTE PointerPte;
484 PMMPDE PointerPde;
485 MMPTE TempPte;
486 MMPDE TempPde;
487 ULONG Color, Index;
488 PFN_NUMBER PageFrameIndex;
489 PMM_SESSION_SPACE SessionGlobal;
490 BOOLEAN AllocatedPageTable;
491 PMMWSL WorkingSetList;
492
493 /* Get pointers to session global and the session working set list */
494 SessionGlobal = MmSessionSpace->GlobalVirtualAddress;
495 WorkingSetList = (PMMWSL)MiSessionSpaceWs;
496
497 /* Fill out the two pointers */
498 MmSessionSpace->Vm.VmWorkingSetList = WorkingSetList;
499 MmSessionSpace->Wsle = (PMMWSLE)WorkingSetList->UsedPageTableEntries;
500
501 /* Get the PDE for the working set, and check if it's already allocated */
502 PointerPde = MiAddressToPde(WorkingSetList);
503 if (PointerPde->u.Hard.Valid == 1)
504 {
505 /* Nope, we'll have to do it */
506 #ifndef _M_ARM
507 ASSERT(PointerPde->u.Hard.Global == 0);
508 #endif
509 AllocatedPageTable = FALSE;
510 }
511 else
512 {
513 /* Yep, that makes our job easier */
514 AllocatedPageTable = TRUE;
515 }
516
517 /* Get the PTE for the working set */
518 PointerPte = MiAddressToPte(WorkingSetList);
519
520 /* Initialize the working set lock, and lock the PFN database */
521 ExInitializePushLock(&SessionGlobal->Vm.WorkingSetMutex);
522 //MmLockPageableSectionByHandle(ExPageLockHandle);
523 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
524
525 /* Check if we need a page table */
526 if (AllocatedPageTable != FALSE)
527 {
528 /* Get a zeroed colored zero page */
529 Color = MI_GET_NEXT_COLOR();
530 PageFrameIndex = MiRemoveZeroPageSafe(Color);
531 if (!PageFrameIndex)
532 {
533 /* No zero pages, grab a free one */
534 PageFrameIndex = MiRemoveAnyPage(Color);
535
536 /* Zero it outside the PFN lock */
537 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
538 MiZeroPhysicalPage(PageFrameIndex);
539 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
540 }
541
542 /* Write a valid PDE for it */
543 TempPde.u.Long = ValidKernelPdeLocal.u.Long;
544 TempPde.u.Hard.PageFrameNumber = PageFrameIndex;
545 MI_WRITE_VALID_PDE(PointerPde, TempPde);
546
547 /* Add this into the list */
548 Index = ((ULONG_PTR)WorkingSetList - (ULONG_PTR)MmSessionBase) >> 22;
549 #ifndef _M_AMD64
550 MmSessionSpace->PageTables[Index] = TempPde;
551 #endif
552 /* Initialize the page directory page, and now zero the working set list itself */
553 MiInitializePfnForOtherProcess(PageFrameIndex,
554 PointerPde,
555 MmSessionSpace->SessionPageDirectoryIndex);
556 KeZeroPages(PointerPte, PAGE_SIZE);
557 }
558
559 /* Get a zeroed colored zero page */
560 Color = MI_GET_NEXT_COLOR();
561 PageFrameIndex = MiRemoveZeroPageSafe(Color);
562 if (!PageFrameIndex)
563 {
564 /* No zero pages, grab a free one */
565 PageFrameIndex = MiRemoveAnyPage(Color);
566
567 /* Zero it outside the PFN lock */
568 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
569 MiZeroPhysicalPage(PageFrameIndex);
570 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
571 }
572
573 /* Write a valid PTE for it */
574 TempPte.u.Long = ValidKernelPteLocal.u.Long;
575 MI_MAKE_DIRTY_PAGE(&TempPte);
576 TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
577
578 /* Initialize the working set list page */
579 MiInitializePfnAndMakePteValid(PageFrameIndex, PointerPte, TempPte);
580
581 /* Now we can release the PFN database lock */
582 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
583
584 /* Fill out the working set structure */
585 MmSessionSpace->Vm.Flags.SessionSpace = 1;
586 MmSessionSpace->Vm.MinimumWorkingSetSize = 20;
587 MmSessionSpace->Vm.MaximumWorkingSetSize = 384;
588 WorkingSetList->LastEntry = 20;
589 WorkingSetList->HashTable = NULL;
590 WorkingSetList->HashTableSize = 0;
591 WorkingSetList->Wsle = MmSessionSpace->Wsle;
592
593 /* Acquire the expansion lock while touching the session */
594 OldIrql = MiAcquireExpansionLock();
595
596 /* Handle list insertions */
597 ASSERT(SessionGlobal->WsListEntry.Flink == NULL);
598 ASSERT(SessionGlobal->WsListEntry.Blink == NULL);
599 InsertTailList(&MiSessionWsList, &SessionGlobal->WsListEntry);
600
601 ASSERT(SessionGlobal->Vm.WorkingSetExpansionLinks.Flink == NULL);
602 ASSERT(SessionGlobal->Vm.WorkingSetExpansionLinks.Blink == NULL);
603 InsertTailList(&MmWorkingSetExpansionHead,
604 &SessionGlobal->Vm.WorkingSetExpansionLinks);
605
606 /* Release the lock again */
607 MiReleaseExpansionLock(OldIrql);
608
609 /* All done, return */
610 //MmUnlockPageableImageSection(ExPageLockHandle);
611 return STATUS_SUCCESS;
612 }
613
614 NTSTATUS
615 NTAPI
616 MiSessionCreateInternal(OUT PULONG SessionId)
617 {
618 PEPROCESS Process = PsGetCurrentProcess();
619 ULONG NewFlags, Flags, Size, i, Color;
620 KIRQL OldIrql;
621 PMMPTE PointerPte, SessionPte;
622 PMMPDE PointerPde, PageTables;
623 PMM_SESSION_SPACE SessionGlobal;
624 MMPTE TempPte;
625 MMPDE TempPde;
626 NTSTATUS Status;
627 BOOLEAN Result;
628 PFN_NUMBER SessionPageDirIndex;
629 PFN_NUMBER TagPage[MI_SESSION_TAG_PAGES_MAXIMUM];
630 PFN_NUMBER DataPage[MI_SESSION_DATA_PAGES_MAXIMUM];
631
632 /* This should not exist yet */
633 ASSERT(MmIsAddressValid(MmSessionSpace) == FALSE);
634
635 /* Loop so we can set the session-is-creating flag */
636 Flags = Process->Flags;
637 while (TRUE)
638 {
639 /* Check if it's already set */
640 if (Flags & PSF_SESSION_CREATION_UNDERWAY_BIT)
641 {
642 /* Bail out */
643 DPRINT1("Lost session race\n");
644 return STATUS_ALREADY_COMMITTED;
645 }
646
647 /* Now try to set it */
648 NewFlags = InterlockedCompareExchange((PLONG)&Process->Flags,
649 Flags | PSF_SESSION_CREATION_UNDERWAY_BIT,
650 Flags);
651 if (NewFlags == Flags) break;
652
653 /* It changed, try again */
654 Flags = NewFlags;
655 }
656
657 /* Now we should own the flag */
658 ASSERT(Process->Flags & PSF_SESSION_CREATION_UNDERWAY_BIT);
659
660 /*
661 * Session space covers everything from 0xA0000000 to 0xC0000000.
662 * Allocate enough page tables to describe the entire region
663 */
664 Size = (0x20000000 / PDE_MAPPED_VA) * sizeof(MMPTE);
665 PageTables = ExAllocatePoolWithTag(NonPagedPool, Size, 'tHmM');
666 ASSERT(PageTables != NULL);
667 RtlZeroMemory(PageTables, Size);
668
669 /* Lock the session ID creation mutex */
670 KeAcquireGuardedMutex(&MiSessionIdMutex);
671
672 /* Allocate a new Session ID */
673 *SessionId = RtlFindClearBitsAndSet(MiSessionIdBitmap, 1, 0);
674 if (*SessionId == 0xFFFFFFFF)
675 {
676 /* We ran out of session IDs, we should expand */
677 DPRINT1("Too many sessions created. Expansion not yet supported\n");
678 ExFreePoolWithTag(PageTables, 'tHmM');
679 return STATUS_NO_MEMORY;
680 }
681
682 /* Unlock the session ID creation mutex */
683 KeReleaseGuardedMutex(&MiSessionIdMutex);
684
685 /* Reserve the global PTEs */
686 SessionPte = MiReserveSystemPtes(MiSessionDataPages, SystemPteSpace);
687 ASSERT(SessionPte != NULL);
688
689 /* Acquire the PFN lock while we set everything up */
690 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
691
692 /* Loop the global PTEs */
693 TempPte.u.Long = ValidKernelPte.u.Long;
694 for (i = 0; i < MiSessionDataPages; i++)
695 {
696 /* Get a zeroed colored zero page */
697 Color = MI_GET_NEXT_COLOR();
698 DataPage[i] = MiRemoveZeroPageSafe(Color);
699 if (!DataPage[i])
700 {
701 /* No zero pages, grab a free one */
702 DataPage[i] = MiRemoveAnyPage(Color);
703
704 /* Zero it outside the PFN lock */
705 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
706 MiZeroPhysicalPage(DataPage[i]);
707 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
708 }
709
710 /* Fill the PTE out */
711 TempPte.u.Hard.PageFrameNumber = DataPage[i];
712 MI_WRITE_VALID_PTE(SessionPte + i, TempPte);
713 }
714
715 /* Set the pointer to global space */
716 SessionGlobal = MiPteToAddress(SessionPte);
717
718 /* Get a zeroed colored zero page */
719 Color = MI_GET_NEXT_COLOR();
720 SessionPageDirIndex = MiRemoveZeroPageSafe(Color);
721 if (!SessionPageDirIndex)
722 {
723 /* No zero pages, grab a free one */
724 SessionPageDirIndex = MiRemoveAnyPage(Color);
725
726 /* Zero it outside the PFN lock */
727 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
728 MiZeroPhysicalPage(SessionPageDirIndex);
729 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
730 }
731
732 /* Fill the PTE out */
733 TempPde.u.Long = ValidKernelPdeLocal.u.Long;
734 TempPde.u.Hard.PageFrameNumber = SessionPageDirIndex;
735
736 /* Setup, allocate, fill out the MmSessionSpace PTE */
737 PointerPde = MiAddressToPde(MmSessionSpace);
738 ASSERT(PointerPde->u.Long == 0);
739 MI_WRITE_VALID_PDE(PointerPde, TempPde);
740 MiInitializePfnForOtherProcess(SessionPageDirIndex,
741 PointerPde,
742 SessionPageDirIndex);
743 ASSERT(MI_PFN_ELEMENT(SessionPageDirIndex)->u1.WsIndex == 0);
744
745 /* Loop all the local PTEs for it */
746 TempPte.u.Long = ValidKernelPteLocal.u.Long;
747 PointerPte = MiAddressToPte(MmSessionSpace);
748 for (i = 0; i < MiSessionDataPages; i++)
749 {
750 /* And fill them out */
751 TempPte.u.Hard.PageFrameNumber = DataPage[i];
752 MiInitializePfnAndMakePteValid(DataPage[i], PointerPte + i, TempPte);
753 ASSERT(MI_PFN_ELEMENT(DataPage[i])->u1.WsIndex == 0);
754 }
755
756 /* Finally loop all of the session pool tag pages */
757 for (i = 0; i < MiSessionTagPages; i++)
758 {
759 /* Grab a zeroed colored page */
760 Color = MI_GET_NEXT_COLOR();
761 TagPage[i] = MiRemoveZeroPageSafe(Color);
762 if (!TagPage[i])
763 {
764 /* No zero pages, grab a free one */
765 TagPage[i] = MiRemoveAnyPage(Color);
766
767 /* Zero it outside the PFN lock */
768 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
769 MiZeroPhysicalPage(TagPage[i]);
770 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
771 }
772
773 /* Fill the PTE out */
774 TempPte.u.Hard.PageFrameNumber = TagPage[i];
775 MiInitializePfnAndMakePteValid(TagPage[i],
776 PointerPte + MiSessionDataPages + i,
777 TempPte);
778 }
779
780 /* PTEs have been setup, release the PFN lock */
781 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
782
783 /* Fill out the session space structure now */
784 MmSessionSpace->GlobalVirtualAddress = SessionGlobal;
785 MmSessionSpace->ReferenceCount = 1;
786 MmSessionSpace->ResidentProcessCount = 1;
787 MmSessionSpace->u.LongFlags = 0;
788 MmSessionSpace->SessionId = *SessionId;
789 MmSessionSpace->LocaleId = PsDefaultSystemLocaleId;
790 MmSessionSpace->SessionPageDirectoryIndex = SessionPageDirIndex;
791 MmSessionSpace->Color = Color;
792 MmSessionSpace->NonPageablePages = MiSessionCreateCharge;
793 MmSessionSpace->CommittedPages = MiSessionCreateCharge;
794 #ifndef _M_AMD64
795 MmSessionSpace->PageTables = PageTables;
796 MmSessionSpace->PageTables[PointerPde - MiAddressToPde(MmSessionBase)] = *PointerPde;
797 #endif
798 InitializeListHead(&MmSessionSpace->ImageList);
799 DPRINT1("Session %lu is ready to go: 0x%p 0x%p, %lx 0x%p\n",
800 *SessionId, MmSessionSpace, SessionGlobal, SessionPageDirIndex, PageTables);
801
802 /* Initialize session pool */
803 //Status = MiInitializeSessionPool();
804 Status = STATUS_SUCCESS;
805 ASSERT(NT_SUCCESS(Status) == TRUE);
806
807 /* Initialize system space */
808 Result = MiInitializeSystemSpaceMap(&SessionGlobal->Session);
809 ASSERT(Result == TRUE);
810
811 /* Initialize the process list, make sure the workign set list is empty */
812 ASSERT(SessionGlobal->WsListEntry.Flink == NULL);
813 ASSERT(SessionGlobal->WsListEntry.Blink == NULL);
814 InitializeListHead(&SessionGlobal->ProcessList);
815
816 /* We're done, clear the flag */
817 ASSERT(Process->Flags & PSF_SESSION_CREATION_UNDERWAY_BIT);
818 PspClearProcessFlag(Process, PSF_SESSION_CREATION_UNDERWAY_BIT);
819
820 /* Insert the process into the session */
821 ASSERT(Process->Session == NULL);
822 ASSERT(SessionGlobal->ProcessReferenceToSession == 0);
823 SessionGlobal->ProcessReferenceToSession = 1;
824
825 /* We're done */
826 InterlockedIncrement(&MmSessionDataPages);
827 return STATUS_SUCCESS;
828 }
829
830 NTSTATUS
831 NTAPI
832 MmSessionCreate(OUT PULONG SessionId)
833 {
834 PEPROCESS Process = PsGetCurrentProcess();
835 ULONG SessionLeaderExists;
836 NTSTATUS Status;
837
838 /* Fail if the process is already in a session */
839 if (Process->Flags & PSF_PROCESS_IN_SESSION_BIT)
840 {
841 DPRINT1("Process already in session\n");
842 return STATUS_ALREADY_COMMITTED;
843 }
844
845 /* Check if the process is already the session leader */
846 if (!Process->Vm.Flags.SessionLeader)
847 {
848 /* Atomically set it as the leader */
849 SessionLeaderExists = InterlockedCompareExchange(&MiSessionLeaderExists, 1, 0);
850 if (SessionLeaderExists)
851 {
852 DPRINT1("Session leader race\n");
853 return STATUS_INVALID_SYSTEM_SERVICE;
854 }
855
856 /* Do the work required to upgrade him */
857 MiSessionLeader(Process);
858 }
859
860 /* Create the session */
861 KeEnterCriticalRegion();
862 Status = MiSessionCreateInternal(SessionId);
863 if (!NT_SUCCESS(Status))
864 {
865 KeLeaveCriticalRegion();
866 return Status;
867 }
868
869 /* Set up the session working set */
870 Status = MiSessionInitializeWorkingSetList();
871 if (!NT_SUCCESS(Status))
872 {
873 /* Fail */
874 //MiDereferenceSession();
875 ASSERT(FALSE);
876 KeLeaveCriticalRegion();
877 return Status;
878 }
879
880 /* All done */
881 KeLeaveCriticalRegion();
882
883 /* Set and assert the flags, and return */
884 MmSessionSpace->u.Flags.Initialized = 1;
885 PspSetProcessFlag(Process, PSF_PROCESS_IN_SESSION_BIT);
886 ASSERT(MiSessionLeaderExists == 1);
887 return Status;
888 }
889
890 NTSTATUS
891 NTAPI
892 MmSessionDelete(IN ULONG SessionId)
893 {
894 PEPROCESS Process = PsGetCurrentProcess();
895
896 /* Process must be in a session */
897 if (!(Process->Flags & PSF_PROCESS_IN_SESSION_BIT))
898 {
899 DPRINT1("Not in a session!\n");
900 return STATUS_UNABLE_TO_FREE_VM;
901 }
902
903 /* It must be the session leader */
904 if (!Process->Vm.Flags.SessionLeader)
905 {
906 DPRINT1("Not a session leader!\n");
907 return STATUS_UNABLE_TO_FREE_VM;
908 }
909
910 /* Remove one reference count */
911 KeEnterCriticalRegion();
912 /* FIXME: Do it */
913 KeLeaveCriticalRegion();
914
915 /* All done */
916 return STATUS_SUCCESS;
917 }
918
919 _IRQL_requires_max_(APC_LEVEL)
920 NTSTATUS
921 NTAPI
922 MmAttachSession(
923 _Inout_ PVOID SessionEntry,
924 _Out_ PKAPC_STATE ApcState)
925 {
926 PEPROCESS EntryProcess;
927 PMM_SESSION_SPACE EntrySession, CurrentSession;
928 PEPROCESS CurrentProcess;
929 KIRQL OldIrql;
930
931 /* The parameter is the actual process! */
932 EntryProcess = SessionEntry;
933 NT_ASSERT(EntryProcess != NULL);
934
935 /* Sanity checks */
936 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
937 ASSERT(EntryProcess->Vm.Flags.SessionLeader == 0);
938
939 /* Get the session from the process that was passed in */
940 EntrySession = EntryProcess->Session;
941 ASSERT(EntrySession != NULL);
942
943 /* Get the current process and it's session */
944 CurrentProcess = PsGetCurrentProcess();
945 CurrentSession = CurrentProcess->Session;
946
947 /* Acquire the expansion lock while touching the session */
948 OldIrql = MiAcquireExpansionLock();
949
950 /* Check if the session is about to be deleted */
951 if (EntrySession->u.Flags.DeletePending)
952 {
953 /* We cannot attach to it, so unlock and fail */
954 MiReleaseExpansionLock(OldIrql);
955 return STATUS_PROCESS_IS_TERMINATING;
956 }
957
958 /* Count the number of attaches */
959 EntrySession->AttachCount++;
960
961 /* we can release the lock again */
962 MiReleaseExpansionLock(OldIrql);
963
964 /* Check if we are not the session leader and we are in a session */
965 if (!CurrentProcess->Vm.Flags.SessionLeader && (CurrentSession != NULL))
966 {
967 /* Are we already in the right session? */
968 if (CurrentSession == EntrySession)
969 {
970 /* We are, so "attach" to the current process */
971 EntryProcess = CurrentProcess;
972 }
973 else
974 {
975 /* We are not, the session id should better not match! */
976 ASSERT(CurrentSession->SessionId != EntrySession->SessionId);
977 }
978 }
979
980 /* Now attach to the process that we have */
981 KeStackAttachProcess(&EntryProcess->Pcb, ApcState);
982
983 /* Success! */
984 return STATUS_SUCCESS;
985 }
986
987 _IRQL_requires_max_(APC_LEVEL)
988 VOID
989 NTAPI
990 MmDetachSession(
991 _Inout_ PVOID SessionEntry,
992 _In_ PKAPC_STATE ApcState)
993 {
994 PEPROCESS EntryProcess;
995 PMM_SESSION_SPACE EntrySession;
996 KIRQL OldIrql;
997 BOOLEAN DeletePending;
998
999 /* The parameter is the actual process! */
1000 EntryProcess = SessionEntry;
1001 NT_ASSERT(EntryProcess != NULL);
1002
1003 /* Sanity checks */
1004 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
1005 ASSERT(EntryProcess->Vm.Flags.SessionLeader == 0);
1006
1007 /* Get the session from the process that was passed in */
1008 EntrySession = EntryProcess->Session;
1009 ASSERT(EntrySession != NULL);
1010
1011 /* Acquire the expansion lock while touching the session */
1012 OldIrql = MiAcquireExpansionLock();
1013
1014 /* Make sure we have at least one attach and decrement the count */
1015 ASSERT(EntrySession->AttachCount >= 1);
1016 EntrySession->AttachCount--;
1017
1018 /* Remember if a delete is pending and we were the last one attached */
1019 DeletePending = EntrySession->u.Flags.DeletePending &&
1020 (EntrySession->AttachCount == 0);
1021
1022 /* Release the lock again */
1023 MiReleaseExpansionLock(OldIrql);
1024
1025 /* Detach from the process */
1026 KeUnstackDetachProcess(ApcState);
1027
1028 /* Check if we need to set the attach event */
1029 if (DeletePending)
1030 KeSetEvent(&EntrySession->AttachEvent, IO_NO_INCREMENT, FALSE);
1031 }
1032
1033 VOID
1034 NTAPI
1035 MmQuitNextSession(
1036 _Inout_ PVOID SessionEntry)
1037 {
1038 PEPROCESS EntryProcess;
1039
1040 /* The parameter is the actual process! */
1041 EntryProcess = SessionEntry;
1042 NT_ASSERT(EntryProcess != NULL);
1043
1044 /* Sanity checks */
1045 ASSERT(KeGetCurrentIrql () <= APC_LEVEL);
1046 ASSERT(EntryProcess->Vm.Flags.SessionLeader == 0);
1047 ASSERT(EntryProcess->Session != NULL);
1048
1049 /* Get rid of the reference we took */
1050 ObDereferenceObject(EntryProcess);
1051 }
1052
1053 PVOID
1054 NTAPI
1055 MmGetSessionById(
1056 _In_ ULONG SessionId)
1057 {
1058 PLIST_ENTRY ListEntry;
1059 PMM_SESSION_SPACE Session;
1060 PEPROCESS Process = NULL;
1061 KIRQL OldIrql;
1062
1063 /* Acquire the expansion lock while touching the session */
1064 OldIrql = MiAcquireExpansionLock();
1065
1066 /* Loop all entries in the session ws list */
1067 ListEntry = MiSessionWsList.Flink;
1068 while (ListEntry != &MiSessionWsList)
1069 {
1070 Session = CONTAINING_RECORD(ListEntry, MM_SESSION_SPACE, WsListEntry);
1071
1072 /* Check if this is the session we are looking for */
1073 if (Session->SessionId == SessionId)
1074 {
1075 /* Check if we also have a process in the process list */
1076 if (!IsListEmpty(&Session->ProcessList))
1077 {
1078 Process = CONTAINING_RECORD(Session->ProcessList.Flink,
1079 EPROCESS,
1080 SessionProcessLinks);
1081
1082 /* Reference the process */
1083 ObReferenceObject(Process);
1084 break;
1085 }
1086 }
1087 }
1088
1089 /* Release the lock again */
1090 MiReleaseExpansionLock(OldIrql);
1091
1092 return Process;
1093 }