[NTOS:MM]
[reactos.git] / reactos / ntoskrnl / mm / marea.c
1 /*
2 * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 * PROJECT: ReactOS kernel
20 * FILE: ntoskrnl/mm/marea.c
21 * PURPOSE: Implements memory areas
22 *
23 * PROGRAMMERS: Rex Jolliff
24 * David Welch
25 * Eric Kohl
26 * Philip Susi
27 * Casper Hornstrup
28 * Eric Kohl
29 * Ge van Geldorp
30 * Royce Mitchell III
31 * Aleksey Bragin
32 * Jason Filby
33 * Thomas Weidenmueller
34 * Gunnar Andre' Dalsnes
35 * Mike Nordell
36 * Alex Ionescu
37 * Filip Navara
38 * Herve Poussineau
39 * Steven Edwards
40 */
41
42 /* INCLUDES *****************************************************************/
43
44 #include <ntoskrnl.h>
45 #define NDEBUG
46 #include <cache/section/newmm.h>
47 #include <debug.h>
48
49 #include "ARM3/miarm.h"
50
51 MEMORY_AREA MiStaticMemoryAreas[MI_STATIC_MEMORY_AREAS];
52 ULONG MiStaticMemoryAreaCount;
53
54 MM_AVL_TABLE MiRosKernelVadRoot;
55 BOOLEAN MiRosKernelVadRootInitialized;
56
57 /* FUNCTIONS *****************************************************************/
58
59 PMEMORY_AREA NTAPI
60 MmLocateMemoryAreaByAddress(
61 PMMSUPPORT AddressSpace,
62 PVOID Address_)
63 {
64 ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
65 PEPROCESS Process;
66 PMM_AVL_TABLE Table;
67 PMMADDRESS_NODE Node;
68 PMEMORY_AREA MemoryArea;
69 TABLE_SEARCH_RESULT Result;
70 PMMVAD_LONG Vad;
71
72 Process = MmGetAddressSpaceOwner(AddressSpace);
73 Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
74
75 Result = MiCheckForConflictingNode(StartVpn, StartVpn, Table, &Node);
76 if (Result != TableFoundNode)
77 {
78 return NULL;
79 }
80
81 Vad = (PMMVAD_LONG)Node;
82 if (Vad->u.VadFlags.Spare == 0)
83 {
84 /* Check if this is VM VAD */
85 if (Vad->ControlArea == NULL)
86 {
87 /* We store the reactos MEMORY_AREA here */
88 MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
89 }
90 else
91 {
92 /* This is a section VAD. Store the MAREA here for now */
93 MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
94 }
95 }
96 else
97 {
98 MemoryArea = (PMEMORY_AREA)Node;
99 }
100
101 return MemoryArea;
102 }
103
104 PMEMORY_AREA
105 NTAPI
106 MmLocateMemoryAreaByRegion(
107 PMMSUPPORT AddressSpace,
108 PVOID Address_,
109 ULONG_PTR Length)
110 {
111 ULONG_PTR StartVpn = (ULONG_PTR)Address_ / PAGE_SIZE;
112 ULONG_PTR EndVpn = ((ULONG_PTR)Address_ + Length - 1) / PAGE_SIZE;
113 PEPROCESS Process;
114 PMM_AVL_TABLE Table;
115 PMMADDRESS_NODE Node;
116 PMEMORY_AREA MemoryArea;
117 TABLE_SEARCH_RESULT Result;
118 PMMVAD_LONG Vad;
119
120 Process = MmGetAddressSpaceOwner(AddressSpace);
121 Table = (Process != NULL) ? &Process->VadRoot : &MiRosKernelVadRoot;
122
123 Result = MiCheckForConflictingNode(StartVpn, EndVpn, Table, &Node);
124 if (Result != TableFoundNode)
125 {
126 return NULL;
127 }
128
129 Vad = (PMMVAD_LONG)Node;
130 if (Vad->u.VadFlags.Spare == 0)
131 {
132 /* Check if this is VM VAD */
133 if (Vad->ControlArea == NULL)
134 {
135 /* We store the reactos MEMORY_AREA here */
136 MemoryArea = (PMEMORY_AREA)Vad->FirstPrototypePte;
137 }
138 else
139 {
140 /* This is a section VAD. Store the MAREA here for now */
141 MemoryArea = (PMEMORY_AREA)Vad->u4.Banked;
142 }
143 }
144 else
145 {
146 MemoryArea = (PMEMORY_AREA)Node;
147 }
148
149 ASSERT(MemoryArea != NULL);
150 return MemoryArea;
151 }
152
153 VOID
154 NTAPI
155 MiInsertVad(IN PMMVAD Vad,
156 IN PMM_AVL_TABLE VadRoot);
157
158 ULONG
159 NTAPI
160 MiMakeProtectionMask(
161 IN ULONG Protect
162 );
163
164
165 static VOID
166 MmInsertMemoryArea(
167 PMMSUPPORT AddressSpace,
168 PMEMORY_AREA marea)
169 {
170 PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
171
172 marea->VadNode.StartingVpn = marea->StartingVpn;
173 marea->VadNode.EndingVpn = marea->EndingVpn;
174 marea->VadNode.u.VadFlags.Spare = 1;
175 marea->VadNode.u.VadFlags.Protection = MiMakeProtectionMask(marea->Protect);
176
177 /* Build a lame VAD if this is a user-space allocation */
178 if (MA_GetEndingAddress(marea) < (ULONG_PTR)MmSystemRangeStart)
179 {
180 ASSERT(Process != NULL);
181 if (marea->Type != MEMORY_AREA_OWNED_BY_ARM3)
182 {
183 ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW || marea->Type == MEMORY_AREA_CACHE);
184
185 /* Insert the VAD */
186 MiLockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
187 MiInsertVad(&marea->VadNode, &Process->VadRoot);
188 MiUnlockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
189 marea->Vad = &marea->VadNode;
190 }
191 }
192 else
193 {
194 ASSERT(Process == NULL);
195
196 if (!MiRosKernelVadRootInitialized)
197 {
198 MiRosKernelVadRoot.BalancedRoot.u1.Parent = &MiRosKernelVadRoot.BalancedRoot;
199 MiRosKernelVadRoot.Unused = 1;
200 MiRosKernelVadRootInitialized = TRUE;
201 }
202
203 /* Insert the VAD */
204 MiLockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
205 MiInsertVad(&marea->VadNode, &MiRosKernelVadRoot);
206 MiUnlockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
207 marea->Vad = NULL;
208 }
209 }
210
211 PVOID NTAPI
212 MmFindGap(
213 PMMSUPPORT AddressSpace,
214 ULONG_PTR Length,
215 ULONG_PTR Granularity,
216 BOOLEAN TopDown)
217 {
218 PEPROCESS Process;
219 PMM_AVL_TABLE VadRoot;
220 TABLE_SEARCH_RESULT Result;
221 PMMADDRESS_NODE Parent;
222 ULONG_PTR StartingAddress, HighestAddress;
223
224 Process = MmGetAddressSpaceOwner(AddressSpace);
225 VadRoot = Process ? &Process->VadRoot : &MiRosKernelVadRoot;
226 if (TopDown)
227 {
228 /* Find an address top-down */
229 HighestAddress = Process ? (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS : (LONG_PTR)-1;
230 Result = MiFindEmptyAddressRangeDownTree(Length,
231 HighestAddress,
232 Granularity,
233 VadRoot,
234 &StartingAddress,
235 &Parent);
236 }
237 else
238 {
239 Result = MiFindEmptyAddressRangeInTree(Length,
240 Granularity,
241 VadRoot,
242 &Parent,
243 &StartingAddress);
244 }
245
246 if (Result == TableFoundNode)
247 {
248 return NULL;
249 }
250
251 return (PVOID)StartingAddress;
252 }
253
254 VOID
255 NTAPI
256 MiRemoveNode(IN PMMADDRESS_NODE Node,
257 IN PMM_AVL_TABLE Table);
258
259
260 /**
261 * @name MmFreeMemoryArea
262 *
263 * Free an existing memory area.
264 *
265 * @param AddressSpace
266 * Address space to free the area from.
267 * @param MemoryArea
268 * Memory area we're about to free.
269 * @param FreePage
270 * Callback function for each freed page.
271 * @param FreePageContext
272 * Context passed to the callback function.
273 *
274 * @return Status
275 *
276 * @remarks Lock the address space before calling this function.
277 */
278 VOID
279 NTAPI
280 MiDeletePte(IN PMMPTE PointerPte,
281 IN PVOID VirtualAddress,
282 IN PEPROCESS CurrentProcess,
283 IN PMMPTE PrototypePte);
284
285 NTSTATUS NTAPI
286 MmFreeMemoryArea(
287 PMMSUPPORT AddressSpace,
288 PMEMORY_AREA MemoryArea,
289 PMM_FREE_PAGE_FUNC FreePage,
290 PVOID FreePageContext)
291 {
292 ULONG_PTR Address;
293 PVOID EndAddress;
294
295 /* Make sure we own the address space lock! */
296 ASSERT(CONTAINING_RECORD(AddressSpace, EPROCESS, Vm)->AddressCreationLock.Owner == KeGetCurrentThread());
297
298 /* Check magic */
299 ASSERT(MemoryArea->Magic == 'erAM');
300
301 if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
302 {
303 PEPROCESS CurrentProcess = PsGetCurrentProcess();
304 PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
305
306 if (Process != NULL &&
307 Process != CurrentProcess)
308 {
309 KeAttachProcess(&Process->Pcb);
310 }
311
312 EndAddress = MM_ROUND_UP(MA_GetEndingAddress(MemoryArea), PAGE_SIZE);
313 for (Address = MA_GetStartingAddress(MemoryArea);
314 Address < (ULONG_PTR)EndAddress;
315 Address += PAGE_SIZE)
316 {
317 BOOLEAN Dirty = FALSE;
318 SWAPENTRY SwapEntry = 0;
319 PFN_NUMBER Page = 0;
320
321 if (MmIsPageSwapEntry(Process, (PVOID)Address))
322 {
323 MmDeletePageFileMapping(Process, (PVOID)Address, &SwapEntry);
324 }
325 else
326 {
327 MmDeleteVirtualMapping(Process, (PVOID)Address, &Dirty, &Page);
328 }
329 if (FreePage != NULL)
330 {
331 FreePage(FreePageContext, MemoryArea, (PVOID)Address,
332 Page, SwapEntry, (BOOLEAN)Dirty);
333 }
334 #if (_MI_PAGING_LEVELS == 2)
335 /* Remove page table reference */
336 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
337 if ((SwapEntry || Page) && ((PVOID)Address < MmSystemRangeStart))
338 {
339 ASSERT(AddressSpace != MmGetKernelAddressSpace());
340 if (MiQueryPageTableReferences((PVOID)Address) == 0)
341 {
342 /* No PTE relies on this PDE. Release it */
343 KIRQL OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
344 PMMPDE PointerPde = MiAddressToPde(Address);
345 ASSERT(PointerPde->u.Hard.Valid == 1);
346 MiDeletePte(PointerPde, MiPdeToPte(PointerPde), Process, NULL);
347 ASSERT(PointerPde->u.Hard.Valid == 0);
348 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
349 }
350 }
351 #endif
352 }
353
354 if (Process != NULL &&
355 Process != CurrentProcess)
356 {
357 KeDetachProcess();
358 }
359
360 //if (MemoryArea->VadNode.StartingVpn < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT
361 if (MemoryArea->Vad)
362 {
363 ASSERT(MA_GetEndingAddress(MemoryArea) < (ULONG_PTR)MmSystemRangeStart);
364 ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW || MemoryArea->Type == MEMORY_AREA_CACHE);
365
366 /* MmCleanProcessAddressSpace might have removed it (and this would be MmDeleteProcessAdressSpace) */
367 ASSERT(MemoryArea->VadNode.u.VadFlags.Spare != 0);
368 if (((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare == 1)
369 {
370 MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &Process->VadRoot);
371 }
372
373 MemoryArea->Vad = NULL;
374 }
375 else
376 {
377 MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &MiRosKernelVadRoot);
378 }
379 }
380
381 ExFreePoolWithTag(MemoryArea, TAG_MAREA);
382
383 DPRINT("MmFreeMemoryAreaByNode() succeeded\n");
384
385 return STATUS_SUCCESS;
386 }
387
388 /**
389 * @name MmCreateMemoryArea
390 *
391 * Create a memory area.
392 *
393 * @param AddressSpace
394 * Address space to create the area in.
395 * @param Type
396 * Type of the memory area.
397 * @param BaseAddress
398 * Base address for the memory area we're about the create. On
399 * input it contains either 0 (auto-assign address) or preferred
400 * address. On output it contains the starting address of the
401 * newly created area.
402 * @param Length
403 * Length of the area to allocate.
404 * @param Attributes
405 * Protection attributes for the memory area.
406 * @param Result
407 * Receives a pointer to the memory area on successful exit.
408 *
409 * @return Status
410 *
411 * @remarks Lock the address space before calling this function.
412 */
413
414 NTSTATUS NTAPI
415 MmCreateMemoryArea(PMMSUPPORT AddressSpace,
416 ULONG Type,
417 PVOID *BaseAddress,
418 ULONG_PTR Length,
419 ULONG Protect,
420 PMEMORY_AREA *Result,
421 ULONG AllocationFlags,
422 ULONG Granularity)
423 {
424 ULONG_PTR tmpLength;
425 PMEMORY_AREA MemoryArea;
426 ULONG_PTR EndingAddress;
427
428 DPRINT("MmCreateMemoryArea(Type 0x%lx, BaseAddress %p, "
429 "*BaseAddress %p, Length %p, AllocationFlags %x, "
430 "Result %p)\n",
431 Type, BaseAddress, *BaseAddress, Length, AllocationFlags,
432 Result);
433
434 /* Is this a static memory area? */
435 if (Type & MEMORY_AREA_STATIC)
436 {
437 /* Use the static array instead of the pool */
438 ASSERT(MiStaticMemoryAreaCount < MI_STATIC_MEMORY_AREAS);
439 MemoryArea = &MiStaticMemoryAreas[MiStaticMemoryAreaCount++];
440 }
441 else
442 {
443 /* Allocate the memory area from nonpaged pool */
444 MemoryArea = ExAllocatePoolWithTag(NonPagedPool,
445 sizeof(MEMORY_AREA),
446 TAG_MAREA);
447 }
448
449 if (!MemoryArea)
450 {
451 DPRINT1("Not enough memory.\n");
452 return STATUS_NO_MEMORY;
453 }
454
455 RtlZeroMemory(MemoryArea, sizeof(MEMORY_AREA));
456 MemoryArea->Type = Type & ~MEMORY_AREA_STATIC;
457 MemoryArea->Protect = Protect;
458 MemoryArea->Flags = AllocationFlags;
459 MemoryArea->Magic = 'erAM';
460 MemoryArea->DeleteInProgress = FALSE;
461
462 if (*BaseAddress == 0)
463 {
464 tmpLength = (ULONG_PTR)MM_ROUND_UP(Length, PAGE_SIZE);
465 *BaseAddress = MmFindGap(AddressSpace,
466 tmpLength,
467 Granularity,
468 (AllocationFlags & MEM_TOP_DOWN) == MEM_TOP_DOWN);
469 if ((*BaseAddress) == 0)
470 {
471 DPRINT("No suitable gap\n");
472 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
473 return STATUS_NO_MEMORY;
474 }
475
476 MemoryArea->StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
477 MemoryArea->EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
478 MmInsertMemoryArea(AddressSpace, MemoryArea);
479 }
480 else
481 {
482 EndingAddress = ((ULONG_PTR)*BaseAddress + Length - 1) | (PAGE_SIZE - 1);
483 *BaseAddress = ALIGN_DOWN_POINTER_BY(*BaseAddress, Granularity);
484 tmpLength = EndingAddress + 1 - (ULONG_PTR)*BaseAddress;
485
486 if (!MmGetAddressSpaceOwner(AddressSpace) && *BaseAddress < MmSystemRangeStart)
487 {
488 ASSERT(FALSE);
489 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
490 return STATUS_ACCESS_VIOLATION;
491 }
492
493 if (MmGetAddressSpaceOwner(AddressSpace) &&
494 (ULONG_PTR)(*BaseAddress) + tmpLength > (ULONG_PTR)MmSystemRangeStart)
495 {
496 DPRINT("Memory area for user mode address space exceeds MmSystemRangeStart\n");
497 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
498 return STATUS_ACCESS_VIOLATION;
499 }
500
501 /* No need to check ARM3 owned memory areas, the range MUST be free */
502 if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
503 {
504 if (MmLocateMemoryAreaByRegion(AddressSpace,
505 *BaseAddress,
506 tmpLength) != NULL)
507 {
508 DPRINT("Memory area already occupied\n");
509 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
510 return STATUS_CONFLICTING_ADDRESSES;
511 }
512 }
513
514 MemoryArea->StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
515 MemoryArea->EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
516 MmInsertMemoryArea(AddressSpace, MemoryArea);
517 }
518
519 *Result = MemoryArea;
520
521 DPRINT("MmCreateMemoryArea() succeeded (%p)\n", *BaseAddress);
522 return STATUS_SUCCESS;
523 }
524
525 VOID
526 NTAPI
527 MiRosCleanupMemoryArea(
528 PEPROCESS Process,
529 PMMVAD Vad)
530 {
531 PMEMORY_AREA MemoryArea;
532 PVOID BaseAddress;
533 NTSTATUS Status;
534
535 /* We must be called from MmCleanupAddressSpace and nowhere else!
536 Make sure things are as expected... */
537 ASSERT(Process == PsGetCurrentProcess());
538 ASSERT(Process->VmDeleted == TRUE);
539 ASSERT(((PsGetCurrentThread()->ThreadsProcess == Process) &&
540 (Process->ActiveThreads == 1)) ||
541 (Process->ActiveThreads == 0));
542
543 /* We are in cleanup, we don't need to synchronize */
544 MmUnlockAddressSpace(&Process->Vm);
545
546 MemoryArea = (PMEMORY_AREA)Vad;
547 BaseAddress = (PVOID)MA_GetStartingAddress(MemoryArea);
548
549 if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
550 {
551 Status = MiRosUnmapViewOfSection(Process, BaseAddress, 0);
552 }
553 else if (MemoryArea->Type == MEMORY_AREA_CACHE)
554 {
555 Status = MmUnmapViewOfCacheSegment(&Process->Vm, BaseAddress);
556 }
557 else
558 {
559 /* There shouldn't be anything else! */
560 ASSERT(FALSE);
561 }
562
563 /* Make sure this worked! */
564 ASSERT(NT_SUCCESS(Status));
565
566 /* Lock the address space again */
567 MmLockAddressSpace(&Process->Vm);
568 }
569
570 VOID
571 NTAPI
572 MmDeleteProcessAddressSpace2(IN PEPROCESS Process);
573
574 NTSTATUS
575 NTAPI
576 MmDeleteProcessAddressSpace(PEPROCESS Process)
577 {
578 KIRQL OldIrql;
579 PVOID Address;
580
581 DPRINT("MmDeleteProcessAddressSpace(Process %p (%s))\n", Process,
582 Process->ImageFileName);
583
584 #ifndef _M_AMD64
585 OldIrql = MiAcquireExpansionLock();
586 RemoveEntryList(&Process->MmProcessLinks);
587 MiReleaseExpansionLock(OldIrql);
588 #endif
589 MmLockAddressSpace(&Process->Vm);
590
591 /* There should not be any memory areas left! */
592 ASSERT(Process->Vm.WorkingSetExpansionLinks.Flink == NULL);
593
594 #if (_MI_PAGING_LEVELS == 2)
595 {
596 KIRQL OldIrql;
597 PMMPDE pointerPde;
598 /* Attach to Process */
599 KeAttachProcess(&Process->Pcb);
600
601 /* Acquire PFN lock */
602 OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
603
604 for (Address = MI_LOWEST_VAD_ADDRESS;
605 Address < MM_HIGHEST_VAD_ADDRESS;
606 Address =(PVOID)((ULONG_PTR)Address + (PAGE_SIZE * PTE_COUNT)))
607 {
608 /* At this point all references should be dead */
609 if (MiQueryPageTableReferences(Address) != 0)
610 {
611 DPRINT1("Process %p, Address %p, UsedPageTableEntries %lu\n",
612 Process,
613 Address,
614 MiQueryPageTableReferences(Address));
615 ASSERT(MiQueryPageTableReferences(Address) == 0);
616 }
617
618 pointerPde = MiAddressToPde(Address);
619 /* Unlike in ARM3, we don't necesarrily free the PDE page as soon as reference reaches 0,
620 * so we must clean up a bit when process closes */
621 if (pointerPde->u.Hard.Valid)
622 MiDeletePte(pointerPde, MiPdeToPte(pointerPde), Process, NULL);
623 ASSERT(pointerPde->u.Hard.Valid == 0);
624 }
625
626 /* Release lock */
627 KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
628
629 /* Detach */
630 KeDetachProcess();
631 }
632 #endif
633
634 MmUnlockAddressSpace(&Process->Vm);
635
636 DPRINT("Finished MmDeleteProcessAddressSpace()\n");
637 MmDeleteProcessAddressSpace2(Process);
638 return(STATUS_SUCCESS);
639 }
640
641 /* EOF */