[NTOS:MM] Use inline functions to acquire/release the PFN lock.
[reactos.git] / 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.u.VadFlags.Spare = 1;
173 marea->VadNode.u.VadFlags.Protection = MiMakeProtectionMask(marea->Protect);
174
175 /* Build a lame VAD if this is a user-space allocation */
176 if (marea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT)
177 {
178 ASSERT(Process != NULL);
179 if (marea->Type != MEMORY_AREA_OWNED_BY_ARM3)
180 {
181 ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW || marea->Type == MEMORY_AREA_CACHE);
182
183 /* Insert the VAD */
184 MiLockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
185 MiInsertVad(&marea->VadNode, &Process->VadRoot);
186 MiUnlockProcessWorkingSetUnsafe(PsGetCurrentProcess(), PsGetCurrentThread());
187 marea->Vad = &marea->VadNode;
188 }
189 }
190 else
191 {
192 ASSERT(Process == NULL);
193
194 if (!MiRosKernelVadRootInitialized)
195 {
196 MiRosKernelVadRoot.BalancedRoot.u1.Parent = &MiRosKernelVadRoot.BalancedRoot;
197 MiRosKernelVadRoot.Unused = 1;
198 MiRosKernelVadRootInitialized = TRUE;
199 }
200
201 /* Insert the VAD */
202 MiLockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
203 MiInsertVad(&marea->VadNode, &MiRosKernelVadRoot);
204 MiUnlockWorkingSet(PsGetCurrentThread(), &MmSystemCacheWs);
205 marea->Vad = NULL;
206 }
207 }
208
209 PVOID NTAPI
210 MmFindGap(
211 PMMSUPPORT AddressSpace,
212 ULONG_PTR Length,
213 ULONG_PTR Granularity,
214 BOOLEAN TopDown)
215 {
216 PEPROCESS Process;
217 PMM_AVL_TABLE VadRoot;
218 TABLE_SEARCH_RESULT Result;
219 PMMADDRESS_NODE Parent;
220 ULONG_PTR StartingAddress, HighestAddress;
221
222 Process = MmGetAddressSpaceOwner(AddressSpace);
223 VadRoot = Process ? &Process->VadRoot : &MiRosKernelVadRoot;
224 if (TopDown)
225 {
226 /* Find an address top-down */
227 HighestAddress = Process ? (ULONG_PTR)MM_HIGHEST_VAD_ADDRESS : (LONG_PTR)-1;
228 Result = MiFindEmptyAddressRangeDownTree(Length,
229 HighestAddress,
230 Granularity,
231 VadRoot,
232 &StartingAddress,
233 &Parent);
234 }
235 else
236 {
237 Result = MiFindEmptyAddressRangeInTree(Length,
238 Granularity,
239 VadRoot,
240 &Parent,
241 &StartingAddress);
242 }
243
244 if (Result == TableFoundNode)
245 {
246 return NULL;
247 }
248
249 return (PVOID)StartingAddress;
250 }
251
252 VOID
253 NTAPI
254 MiRemoveNode(IN PMMADDRESS_NODE Node,
255 IN PMM_AVL_TABLE Table);
256
257
258 /**
259 * @name MmFreeMemoryArea
260 *
261 * Free an existing memory area.
262 *
263 * @param AddressSpace
264 * Address space to free the area from.
265 * @param MemoryArea
266 * Memory area we're about to free.
267 * @param FreePage
268 * Callback function for each freed page.
269 * @param FreePageContext
270 * Context passed to the callback function.
271 *
272 * @return Status
273 *
274 * @remarks Lock the address space before calling this function.
275 */
276
277 NTSTATUS NTAPI
278 MmFreeMemoryArea(
279 PMMSUPPORT AddressSpace,
280 PMEMORY_AREA MemoryArea,
281 PMM_FREE_PAGE_FUNC FreePage,
282 PVOID FreePageContext)
283 {
284 ULONG_PTR Address;
285 PVOID EndAddress;
286
287 /* Make sure we own the address space lock! */
288 ASSERT(CONTAINING_RECORD(AddressSpace, EPROCESS, Vm)->AddressCreationLock.Owner == KeGetCurrentThread());
289
290 /* Check magic */
291 ASSERT(MemoryArea->Magic == 'erAM');
292
293 if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
294 {
295 PEPROCESS CurrentProcess = PsGetCurrentProcess();
296 PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
297
298 if (Process != NULL &&
299 Process != CurrentProcess)
300 {
301 KeAttachProcess(&Process->Pcb);
302 }
303
304 EndAddress = MM_ROUND_UP(MA_GetEndingAddress(MemoryArea), PAGE_SIZE);
305 for (Address = MA_GetStartingAddress(MemoryArea);
306 Address < (ULONG_PTR)EndAddress;
307 Address += PAGE_SIZE)
308 {
309 BOOLEAN Dirty = FALSE;
310 SWAPENTRY SwapEntry = 0;
311 PFN_NUMBER Page = 0;
312
313 if (MmIsPageSwapEntry(Process, (PVOID)Address))
314 {
315 MmDeletePageFileMapping(Process, (PVOID)Address, &SwapEntry);
316 }
317 else
318 {
319 MmDeleteVirtualMapping(Process, (PVOID)Address, &Dirty, &Page);
320 }
321 if (FreePage != NULL)
322 {
323 FreePage(FreePageContext, MemoryArea, (PVOID)Address,
324 Page, SwapEntry, (BOOLEAN)Dirty);
325 }
326 #if (_MI_PAGING_LEVELS == 2)
327 /* Remove page table reference */
328 ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
329 if ((SwapEntry || Page) && ((PVOID)Address < MmSystemRangeStart))
330 {
331 ASSERT(AddressSpace != MmGetKernelAddressSpace());
332 if (MiQueryPageTableReferences((PVOID)Address) == 0)
333 {
334 /* No PTE relies on this PDE. Release it */
335 KIRQL OldIrql = MiAcquirePfnLock();
336 PMMPDE PointerPde = MiAddressToPde(Address);
337 ASSERT(PointerPde->u.Hard.Valid == 1);
338 MiDeletePte(PointerPde, MiPdeToPte(PointerPde), Process, NULL);
339 ASSERT(PointerPde->u.Hard.Valid == 0);
340 MiReleasePfnLock(OldIrql);
341 }
342 }
343 #endif
344 }
345
346 if (Process != NULL &&
347 Process != CurrentProcess)
348 {
349 KeDetachProcess();
350 }
351
352 //if (MemoryArea->VadNode.StartingVpn < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT
353 if (MemoryArea->Vad)
354 {
355 ASSERT(MemoryArea->VadNode.EndingVpn + 1 < (ULONG_PTR)MmSystemRangeStart >> PAGE_SHIFT);
356 ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW || MemoryArea->Type == MEMORY_AREA_CACHE);
357
358 /* MmCleanProcessAddressSpace might have removed it (and this would be MmDeleteProcessAdressSpace) */
359 ASSERT(MemoryArea->VadNode.u.VadFlags.Spare != 0);
360 if (((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare == 1)
361 {
362 MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &Process->VadRoot);
363 }
364
365 MemoryArea->Vad = NULL;
366 }
367 else
368 {
369 MiRemoveNode((PMMADDRESS_NODE)&MemoryArea->VadNode, &MiRosKernelVadRoot);
370 }
371 }
372
373 #if DBG
374 MemoryArea->Magic = 'daeD';
375 #endif
376 ExFreePoolWithTag(MemoryArea, TAG_MAREA);
377
378 DPRINT("MmFreeMemoryAreaByNode() succeeded\n");
379
380 return STATUS_SUCCESS;
381 }
382
383 /**
384 * @name MmCreateMemoryArea
385 *
386 * Create a memory area.
387 *
388 * @param AddressSpace
389 * Address space to create the area in.
390 * @param Type
391 * Type of the memory area.
392 * @param BaseAddress
393 * Base address for the memory area we're about the create. On
394 * input it contains either 0 (auto-assign address) or preferred
395 * address. On output it contains the starting address of the
396 * newly created area.
397 * @param Length
398 * Length of the area to allocate.
399 * @param Attributes
400 * Protection attributes for the memory area.
401 * @param Result
402 * Receives a pointer to the memory area on successful exit.
403 *
404 * @return Status
405 *
406 * @remarks Lock the address space before calling this function.
407 */
408
409 NTSTATUS NTAPI
410 MmCreateMemoryArea(PMMSUPPORT AddressSpace,
411 ULONG Type,
412 PVOID *BaseAddress,
413 ULONG_PTR Length,
414 ULONG Protect,
415 PMEMORY_AREA *Result,
416 ULONG AllocationFlags,
417 ULONG Granularity)
418 {
419 ULONG_PTR tmpLength;
420 PMEMORY_AREA MemoryArea;
421 ULONG_PTR EndingAddress;
422
423 DPRINT("MmCreateMemoryArea(Type 0x%lx, BaseAddress %p, "
424 "*BaseAddress %p, Length %p, AllocationFlags %x, "
425 "Result %p)\n",
426 Type, BaseAddress, *BaseAddress, Length, AllocationFlags,
427 Result);
428
429 /* Is this a static memory area? */
430 if (Type & MEMORY_AREA_STATIC)
431 {
432 /* Use the static array instead of the pool */
433 ASSERT(MiStaticMemoryAreaCount < MI_STATIC_MEMORY_AREAS);
434 MemoryArea = &MiStaticMemoryAreas[MiStaticMemoryAreaCount++];
435 }
436 else
437 {
438 /* Allocate the memory area from nonpaged pool */
439 MemoryArea = ExAllocatePoolWithTag(NonPagedPool,
440 sizeof(MEMORY_AREA),
441 TAG_MAREA);
442 }
443
444 if (!MemoryArea)
445 {
446 DPRINT1("Not enough memory.\n");
447 return STATUS_NO_MEMORY;
448 }
449
450 RtlZeroMemory(MemoryArea, sizeof(MEMORY_AREA));
451 MemoryArea->Type = Type & ~MEMORY_AREA_STATIC;
452 MemoryArea->Protect = Protect;
453 MemoryArea->Flags = AllocationFlags;
454 MemoryArea->Magic = 'erAM';
455 MemoryArea->DeleteInProgress = FALSE;
456
457 if (*BaseAddress == 0)
458 {
459 tmpLength = (ULONG_PTR)MM_ROUND_UP(Length, PAGE_SIZE);
460 *BaseAddress = MmFindGap(AddressSpace,
461 tmpLength,
462 Granularity,
463 (AllocationFlags & MEM_TOP_DOWN) == MEM_TOP_DOWN);
464 if ((*BaseAddress) == 0)
465 {
466 DPRINT("No suitable gap\n");
467 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
468 return STATUS_NO_MEMORY;
469 }
470
471 MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
472 MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
473 MmInsertMemoryArea(AddressSpace, MemoryArea);
474 }
475 else
476 {
477 EndingAddress = ((ULONG_PTR)*BaseAddress + Length - 1) | (PAGE_SIZE - 1);
478 *BaseAddress = ALIGN_DOWN_POINTER_BY(*BaseAddress, Granularity);
479 tmpLength = EndingAddress + 1 - (ULONG_PTR)*BaseAddress;
480
481 if (!MmGetAddressSpaceOwner(AddressSpace) && *BaseAddress < MmSystemRangeStart)
482 {
483 ASSERT(FALSE);
484 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
485 return STATUS_ACCESS_VIOLATION;
486 }
487
488 if (MmGetAddressSpaceOwner(AddressSpace) &&
489 (ULONG_PTR)(*BaseAddress) + tmpLength > (ULONG_PTR)MmSystemRangeStart)
490 {
491 DPRINT("Memory area for user mode address space exceeds MmSystemRangeStart\n");
492 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
493 return STATUS_ACCESS_VIOLATION;
494 }
495
496 /* No need to check ARM3 owned memory areas, the range MUST be free */
497 if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
498 {
499 if (MmLocateMemoryAreaByRegion(AddressSpace,
500 *BaseAddress,
501 tmpLength) != NULL)
502 {
503 DPRINT("Memory area already occupied\n");
504 if (!(Type & MEMORY_AREA_STATIC)) ExFreePoolWithTag(MemoryArea, TAG_MAREA);
505 return STATUS_CONFLICTING_ADDRESSES;
506 }
507 }
508
509 MemoryArea->VadNode.StartingVpn = (ULONG_PTR)*BaseAddress >> PAGE_SHIFT;
510 MemoryArea->VadNode.EndingVpn = ((ULONG_PTR)*BaseAddress + tmpLength - 1) >> PAGE_SHIFT;
511 MmInsertMemoryArea(AddressSpace, MemoryArea);
512 }
513
514 *Result = MemoryArea;
515
516 DPRINT("MmCreateMemoryArea() succeeded (%p)\n", *BaseAddress);
517 return STATUS_SUCCESS;
518 }
519
520 VOID
521 NTAPI
522 MiRosCleanupMemoryArea(
523 PEPROCESS Process,
524 PMMVAD Vad)
525 {
526 PMEMORY_AREA MemoryArea;
527 PVOID BaseAddress;
528 NTSTATUS Status;
529
530 /* We must be called from MmCleanupAddressSpace and nowhere else!
531 Make sure things are as expected... */
532 ASSERT(Process == PsGetCurrentProcess());
533 ASSERT(Process->VmDeleted == TRUE);
534 ASSERT(((PsGetCurrentThread()->ThreadsProcess == Process) &&
535 (Process->ActiveThreads == 1)) ||
536 (Process->ActiveThreads == 0));
537
538 /* We are in cleanup, we don't need to synchronize */
539 MmUnlockAddressSpace(&Process->Vm);
540
541 MemoryArea = (PMEMORY_AREA)Vad;
542 BaseAddress = (PVOID)MA_GetStartingAddress(MemoryArea);
543
544 if (MemoryArea->Type == MEMORY_AREA_SECTION_VIEW)
545 {
546 Status = MiRosUnmapViewOfSection(Process, BaseAddress, Process->ProcessExiting);
547 }
548 else if (MemoryArea->Type == MEMORY_AREA_CACHE)
549 {
550 Status = MmUnmapViewOfCacheSegment(&Process->Vm, BaseAddress);
551 }
552 else
553 {
554 /* There shouldn't be anything else! */
555 ASSERT(FALSE);
556 }
557
558 /* Make sure this worked! */
559 ASSERT(NT_SUCCESS(Status));
560
561 /* Lock the address space again */
562 MmLockAddressSpace(&Process->Vm);
563 }
564
565 VOID
566 NTAPI
567 MmDeleteProcessAddressSpace2(IN PEPROCESS Process);
568
569 NTSTATUS
570 NTAPI
571 MmDeleteProcessAddressSpace(PEPROCESS Process)
572 {
573 KIRQL OldIrql;
574 PVOID Address;
575
576 DPRINT("MmDeleteProcessAddressSpace(Process %p (%s))\n", Process,
577 Process->ImageFileName);
578
579 #ifndef _M_AMD64
580 OldIrql = MiAcquireExpansionLock();
581 RemoveEntryList(&Process->MmProcessLinks);
582 MiReleaseExpansionLock(OldIrql);
583 #endif
584 MmLockAddressSpace(&Process->Vm);
585
586 /* There should not be any memory areas left! */
587 ASSERT(Process->Vm.WorkingSetExpansionLinks.Flink == NULL);
588
589 #if (_MI_PAGING_LEVELS == 2)
590 {
591 KIRQL OldIrql;
592 PMMPDE pointerPde;
593 /* Attach to Process */
594 KeAttachProcess(&Process->Pcb);
595
596 /* Acquire PFN lock */
597 OldIrql = MiAcquirePfnLock();
598
599 for (Address = MI_LOWEST_VAD_ADDRESS;
600 Address < MM_HIGHEST_VAD_ADDRESS;
601 Address =(PVOID)((ULONG_PTR)Address + (PAGE_SIZE * PTE_COUNT)))
602 {
603 /* At this point all references should be dead */
604 if (MiQueryPageTableReferences(Address) != 0)
605 {
606 DPRINT1("Process %p, Address %p, UsedPageTableEntries %lu\n",
607 Process,
608 Address,
609 MiQueryPageTableReferences(Address));
610 ASSERT(MiQueryPageTableReferences(Address) == 0);
611 }
612
613 pointerPde = MiAddressToPde(Address);
614 /* Unlike in ARM3, we don't necesarrily free the PDE page as soon as reference reaches 0,
615 * so we must clean up a bit when process closes */
616 if (pointerPde->u.Hard.Valid)
617 MiDeletePte(pointerPde, MiPdeToPte(pointerPde), Process, NULL);
618 ASSERT(pointerPde->u.Hard.Valid == 0);
619 }
620
621 /* Release lock */
622 MiReleasePfnLock(OldIrql);
623
624 /* Detach */
625 KeDetachProcess();
626 }
627 #endif
628
629 MmUnlockAddressSpace(&Process->Vm);
630
631 DPRINT("Finished MmDeleteProcessAddressSpace()\n");
632 MmDeleteProcessAddressSpace2(Process);
633 return(STATUS_SUCCESS);
634 }
635
636 /* EOF */