0b1c21996f1fb5d12edd68a42d7b5317fb3afded
[reactos.git] / reactos / subsystems / mvdm / ntvdm / memory.c
1 /*
2 * COPYRIGHT: GPLv2+ - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: memory.c
5 * PURPOSE: Memory Management
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "ntvdm.h"
14 #include "emulator.h"
15 #include "memory.h"
16
17 /* Extra PSDK/NDK Headers */
18 #include <ndk/mmfuncs.h>
19
20 /* PRIVATE VARIABLES **********************************************************/
21
22 typedef struct _MEM_HOOK
23 {
24 LIST_ENTRY Entry;
25 HANDLE hVdd;
26 ULONG Count;
27
28 union
29 {
30 PVDD_MEMORY_HANDLER VddHandler;
31
32 struct
33 {
34 PMEMORY_READ_HANDLER FastReadHandler;
35 PMEMORY_WRITE_HANDLER FastWriteHandler;
36 };
37 };
38 } MEM_HOOK, *PMEM_HOOK;
39
40 static LIST_ENTRY HookList;
41 static PMEM_HOOK PageTable[TOTAL_PAGES] = { NULL };
42
43 /* PRIVATE FUNCTIONS **********************************************************/
44
45 static inline VOID
46 MemFastMoveMemory(OUT VOID UNALIGNED *Destination,
47 IN const VOID UNALIGNED *Source,
48 IN SIZE_T Length)
49 {
50 #if 1
51 /*
52 * We use a switch here to detect small moves of memory, as these
53 * constitute the bulk of our moves.
54 * Using RtlMoveMemory for all these small moves would be slow otherwise.
55 */
56 switch (Length)
57 {
58 case 0:
59 return;
60
61 case sizeof(UCHAR):
62 *(PUCHAR)Destination = *(PUCHAR)Source;
63 return;
64
65 case sizeof(USHORT):
66 *(PUSHORT)Destination = *(PUSHORT)Source;
67 return;
68
69 case sizeof(ULONG):
70 *(PULONG)Destination = *(PULONG)Source;
71 return;
72
73 case sizeof(ULONGLONG):
74 *(PULONGLONG)Destination = *(PULONGLONG)Source;
75 return;
76
77 default:
78 #if defined(__GNUC__)
79 __builtin_memmove(Destination, Source, Length);
80 #else
81 RtlMoveMemory(Destination, Source, Length);
82 #endif
83 }
84
85 #else // defined(_MSC_VER)
86
87 PUCHAR Dest = (PUCHAR)Destination;
88 PUCHAR Src = (PUCHAR)Source;
89
90 SIZE_T Count, NewSize = Length;
91
92 /* Move dword */
93 Count = NewSize >> 2; // NewSize / sizeof(ULONG);
94 NewSize = NewSize & 3; // NewSize % sizeof(ULONG);
95 __movsd(Dest, Src, Count);
96 Dest += Count << 2; // Count * sizeof(ULONG);
97 Src += Count << 2;
98
99 /* Move word */
100 Count = NewSize >> 1; // NewSize / sizeof(USHORT);
101 NewSize = NewSize & 1; // NewSize % sizeof(USHORT);
102 __movsw(Dest, Src, Count);
103 Dest += Count << 1; // Count * sizeof(USHORT);
104 Src += Count << 1;
105
106 /* Move byte */
107 Count = NewSize; // NewSize / sizeof(UCHAR);
108 // NewSize = NewSize; // NewSize % sizeof(UCHAR);
109 __movsb(Dest, Src, Count);
110
111 #endif
112 }
113
114 static
115 inline
116 VOID
117 ReadPage(PMEM_HOOK Hook, ULONG Address, PVOID Buffer, ULONG Size)
118 {
119 if (Hook && !Hook->hVdd && Hook->FastReadHandler)
120 {
121 Hook->FastReadHandler(Address, REAL_TO_PHYS(Address), Size);
122 }
123
124 MemFastMoveMemory(Buffer, REAL_TO_PHYS(Address), Size);
125 }
126
127 static
128 inline
129 VOID
130 WritePage(PMEM_HOOK Hook, ULONG Address, PVOID Buffer, ULONG Size)
131 {
132 if (!Hook
133 || Hook->hVdd
134 || !Hook->FastWriteHandler
135 || Hook->FastWriteHandler(Address, Buffer, Size))
136 {
137 MemFastMoveMemory(REAL_TO_PHYS(Address), Buffer, Size);
138 }
139 }
140
141 /* PUBLIC FUNCTIONS ***********************************************************/
142
143 VOID
144 MemRead(ULONG Address, PVOID Buffer, ULONG Size)
145 {
146 ULONG i, Offset, Length;
147 ULONG FirstPage = Address >> 12;
148 ULONG LastPage = (Address + Size - 1) >> 12;
149
150 if (FirstPage == LastPage)
151 {
152 ReadPage(PageTable[FirstPage], Address, Buffer, Size);
153 }
154 else
155 {
156 for (i = FirstPage; i <= LastPage; i++)
157 {
158 Offset = (i == FirstPage) ? (Address & (PAGE_SIZE - 1)) : 0;
159 Length = ((i == LastPage) ? (Address + Size - (LastPage << 12)) : PAGE_SIZE) - Offset;
160
161 ReadPage(PageTable[i], (i << 12) + Offset, Buffer, Length);
162 Buffer = (PVOID)((ULONG_PTR)Buffer + Length);
163 }
164 }
165 }
166
167 VOID
168 MemWrite(ULONG Address, PVOID Buffer, ULONG Size)
169 {
170 ULONG i, Offset, Length;
171 ULONG FirstPage = Address >> 12;
172 ULONG LastPage = (Address + Size - 1) >> 12;
173
174 if (FirstPage == LastPage)
175 {
176 WritePage(PageTable[FirstPage], Address, Buffer, Size);
177 }
178 else
179 {
180 for (i = FirstPage; i <= LastPage; i++)
181 {
182 Offset = (i == FirstPage) ? (Address & (PAGE_SIZE - 1)) : 0;
183 Length = ((i == LastPage) ? (Address + Size - (LastPage << 12)) : PAGE_SIZE) - Offset;
184
185 WritePage(PageTable[i], (i << 12) + Offset, Buffer, Length);
186 Buffer = (PVOID)((ULONG_PTR)Buffer + Length);
187 }
188 }
189 }
190
191 VOID
192 MemExceptionHandler(ULONG FaultAddress, BOOLEAN Writing)
193 {
194 PMEM_HOOK Hook = PageTable[FaultAddress >> 12];
195 DPRINT("The memory at 0x%08X could not be %s.\n", FaultAddress, Writing ? "written" : "read");
196
197 /* Exceptions are only supposed to happen when using VDD-style memory hooks */
198 ASSERT(FaultAddress < MAX_ADDRESS && Hook != NULL && Hook->hVdd != NULL);
199
200 /* Call the VDD handler */
201 Hook->VddHandler(REAL_TO_PHYS(FaultAddress), (ULONG)Writing);
202 }
203
204 BOOL
205 MemInstallFastMemoryHook(PVOID Address,
206 ULONG Size,
207 PMEMORY_READ_HANDLER ReadHandler,
208 PMEMORY_WRITE_HANDLER WriteHandler)
209 {
210 PMEM_HOOK Hook;
211 ULONG i;
212 ULONG FirstPage = (ULONG_PTR)Address >> 12;
213 ULONG LastPage = ((ULONG_PTR)Address + Size - 1) >> 12;
214 PLIST_ENTRY Pointer;
215
216 /* Make sure none of these pages are already allocated */
217 for (i = FirstPage; i <= LastPage; i++)
218 {
219 if (PageTable[i] != NULL) return FALSE;
220 }
221
222 for (Pointer = HookList.Flink; Pointer != &HookList; Pointer = Pointer->Flink)
223 {
224 Hook = CONTAINING_RECORD(Pointer, MEM_HOOK, Entry);
225
226 if (Hook->hVdd == NULL
227 && Hook->FastReadHandler == ReadHandler
228 && Hook->FastWriteHandler == WriteHandler)
229 {
230 break;
231 }
232 }
233
234 if (Pointer == &HookList)
235 {
236 /* Create and initialize a new hook entry... */
237 Hook = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(*Hook));
238 if (Hook == NULL) return FALSE;
239
240 Hook->hVdd = NULL;
241 Hook->Count = 0;
242 Hook->FastReadHandler = ReadHandler;
243 Hook->FastWriteHandler = WriteHandler;
244
245 /* ... and add it to the list of hooks */
246 InsertTailList(&HookList, &Hook->Entry);
247 }
248
249 /* Increase the number of pages this hook has */
250 Hook->Count += LastPage - FirstPage + 1;
251
252 /* Add the hook entry to the page table */
253 for (i = FirstPage; i <= LastPage; i++) PageTable[i] = Hook;
254
255 return TRUE;
256 }
257
258 BOOL
259 MemRemoveFastMemoryHook(PVOID Address, ULONG Size)
260 {
261 PMEM_HOOK Hook;
262 ULONG i;
263 ULONG FirstPage = (ULONG_PTR)Address >> 12;
264 ULONG LastPage = ((ULONG_PTR)Address + Size - 1) >> 12;
265
266 if (Size == 0) return FALSE;
267
268 for (i = FirstPage; i <= LastPage; i++)
269 {
270 Hook = PageTable[i];
271 if (Hook == NULL || Hook->hVdd != NULL) continue;
272
273 if (--Hook->Count == 0)
274 {
275 /* This hook has no more pages */
276 RemoveEntryList(&Hook->Entry);
277 RtlFreeHeap(RtlGetProcessHeap(), 0, Hook);
278 }
279
280 PageTable[i] = NULL;
281 }
282
283 return TRUE;
284 }
285
286 BOOLEAN
287 MemQueryMemoryZone(ULONG StartAddress, PULONG Length, PBOOLEAN Hooked)
288 {
289 ULONG Page = StartAddress >> 12;
290 if (Page >= TOTAL_PAGES) return FALSE;
291
292 *Length = 0;
293 *Hooked = PageTable[Page] != NULL;
294
295 while (Page < TOTAL_PAGES && (PageTable[Page] != NULL) == *Hooked)
296 {
297 *Length += PAGE_SIZE;
298 Page++;
299 }
300
301 return TRUE;
302 }
303
304 PBYTE
305 WINAPI
306 Sim32pGetVDMPointer(IN ULONG Address,
307 IN BOOLEAN ProtectedMode)
308 {
309 // FIXME
310 UNREFERENCED_PARAMETER(ProtectedMode);
311
312 /*
313 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
314 * or Selector (if ProtectedMode == TRUE )
315 * LOWORD(Address) == Offset
316 */
317 return (PBYTE)FAR_POINTER(Address);
318 }
319
320 PBYTE
321 WINAPI
322 MGetVdmPointer(IN ULONG Address,
323 IN ULONG Size,
324 IN BOOLEAN ProtectedMode)
325 {
326 UNREFERENCED_PARAMETER(Size);
327 return Sim32pGetVDMPointer(Address, ProtectedMode);
328 }
329
330 PVOID
331 WINAPI
332 VdmMapFlat(IN USHORT Segment,
333 IN ULONG Offset,
334 IN VDM_MODE Mode)
335 {
336 // FIXME
337 UNREFERENCED_PARAMETER(Mode);
338
339 return SEG_OFF_TO_PTR(Segment, Offset);
340 }
341
342 #ifndef VdmFlushCache
343
344 BOOL
345 WINAPI
346 VdmFlushCache(IN USHORT Segment,
347 IN ULONG Offset,
348 IN ULONG Size,
349 IN VDM_MODE Mode)
350 {
351 // FIXME
352 UNIMPLEMENTED;
353 return TRUE;
354 }
355
356 #endif
357
358 #ifndef VdmUnmapFlat
359
360 BOOL
361 WINAPI
362 VdmUnmapFlat(IN USHORT Segment,
363 IN ULONG Offset,
364 IN PVOID Buffer,
365 IN VDM_MODE Mode)
366 {
367 // FIXME
368 UNIMPLEMENTED;
369 return TRUE;
370 }
371
372 #endif
373
374 BOOL
375 WINAPI
376 VDDInstallMemoryHook(IN HANDLE hVdd,
377 IN PVOID pStart,
378 IN DWORD dwCount,
379 IN PVDD_MEMORY_HANDLER MemoryHandler)
380 {
381 NTSTATUS Status;
382 PMEM_HOOK Hook;
383 ULONG i;
384 ULONG FirstPage = (ULONG_PTR)PHYS_TO_REAL(pStart) >> 12;
385 ULONG LastPage = ((ULONG_PTR)PHYS_TO_REAL(pStart) + dwCount - 1) >> 12;
386 PVOID Address = (PVOID)REAL_TO_PHYS(FirstPage * PAGE_SIZE);
387 SIZE_T Size = (LastPage - FirstPage + 1) * PAGE_SIZE;
388 PLIST_ENTRY Pointer;
389
390 /* Check validity of the VDD handle */
391 if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
392 {
393 SetLastError(ERROR_INVALID_PARAMETER);
394 return FALSE;
395 }
396
397 if (dwCount == 0) return FALSE;
398
399 /* Make sure none of these pages are already allocated */
400 for (i = FirstPage; i <= LastPage; i++)
401 {
402 if (PageTable[i] != NULL) return FALSE;
403 }
404
405 for (Pointer = HookList.Flink; Pointer != &HookList; Pointer = Pointer->Flink)
406 {
407 Hook = CONTAINING_RECORD(Pointer, MEM_HOOK, Entry);
408 if (Hook->hVdd == hVdd && Hook->VddHandler == MemoryHandler) break;
409 }
410
411 if (Pointer == &HookList)
412 {
413 /* Create and initialize a new hook entry... */
414 Hook = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(*Hook));
415 if (Hook == NULL)
416 {
417 SetLastError(ERROR_OUTOFMEMORY);
418 return FALSE;
419 }
420
421 Hook->hVdd = hVdd;
422 Hook->Count = 0;
423 Hook->VddHandler = MemoryHandler;
424
425 /* ... and add it to the list of hooks */
426 InsertTailList(&HookList, &Hook->Entry);
427 }
428
429 /* Decommit the pages */
430 Status = NtFreeVirtualMemory(NtCurrentProcess(),
431 &Address,
432 &Size,
433 MEM_DECOMMIT);
434 if (!NT_SUCCESS(Status))
435 {
436 if (Pointer == &HookList)
437 {
438 RemoveEntryList(&Hook->Entry);
439 RtlFreeHeap(RtlGetProcessHeap(), 0, Hook);
440 }
441
442 return FALSE;
443 }
444
445 /* Increase the number of pages this hook has */
446 Hook->Count += LastPage - FirstPage + 1;
447
448 /* Add the hook entry to the page table */
449 for (i = FirstPage; i <= LastPage; i++) PageTable[i] = Hook;
450
451 return TRUE;
452 }
453
454 BOOL
455 WINAPI
456 VDDDeInstallMemoryHook(IN HANDLE hVdd,
457 IN PVOID pStart,
458 IN DWORD dwCount)
459 {
460 NTSTATUS Status;
461 PMEM_HOOK Hook;
462 ULONG i;
463 ULONG FirstPage = (ULONG_PTR)PHYS_TO_REAL(pStart) >> 12;
464 ULONG LastPage = ((ULONG_PTR)PHYS_TO_REAL(pStart) + dwCount - 1) >> 12;
465 PVOID Address = (PVOID)REAL_TO_PHYS(FirstPage * PAGE_SIZE);
466 SIZE_T Size = (LastPage - FirstPage + 1) * PAGE_SIZE;
467
468 /* Check validity of the VDD handle */
469 if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
470 {
471 SetLastError(ERROR_INVALID_PARAMETER);
472 return FALSE;
473 }
474
475 if (dwCount == 0) return FALSE;
476
477 /* Commit the pages */
478 Status = NtAllocateVirtualMemory(NtCurrentProcess(),
479 &Address,
480 0,
481 &Size,
482 MEM_COMMIT,
483 PAGE_READWRITE);
484 if (!NT_SUCCESS(Status)) return FALSE;
485
486 for (i = FirstPage; i <= LastPage; i++)
487 {
488 Hook = PageTable[i];
489 if (Hook == NULL) continue;
490
491 if (Hook->hVdd != hVdd)
492 {
493 DPRINT1("VDDDeInstallMemoryHook: Page %u owned by someone else.\n", i);
494 continue;
495 }
496
497 if (--Hook->Count == 0)
498 {
499 /* This hook has no more pages */
500 RemoveEntryList(&Hook->Entry);
501 RtlFreeHeap(RtlGetProcessHeap(), 0, Hook);
502 }
503
504 PageTable[i] = NULL;
505 }
506
507 return TRUE;
508 }
509
510 BOOL
511 WINAPI
512 VDDAllocMem(IN HANDLE hVdd,
513 IN PVOID Address,
514 IN ULONG Size)
515 {
516 NTSTATUS Status;
517 PMEM_HOOK Hook;
518 ULONG i;
519 ULONG FirstPage = (ULONG_PTR)PHYS_TO_REAL(Address) >> 12;
520 ULONG LastPage = ((ULONG_PTR)PHYS_TO_REAL(Address) + Size - 1) >> 12;
521 SIZE_T RealSize = (LastPage - FirstPage + 1) * PAGE_SIZE;
522
523 /* Check validity of the VDD handle */
524 if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
525 {
526 SetLastError(ERROR_INVALID_PARAMETER);
527 return FALSE;
528 }
529
530 if (Size == 0) return FALSE;
531
532 /* Fixup the address */
533 Address = (PVOID)REAL_TO_PHYS(FirstPage * PAGE_SIZE);
534
535 /* Be sure that all the region is held by the VDD */
536 for (i = FirstPage; i <= LastPage; i++)
537 {
538 Hook = PageTable[i];
539 if (Hook == NULL) return FALSE;
540
541 if (Hook->hVdd != hVdd)
542 {
543 DPRINT1("VDDAllocMem: Page %u owned by someone else.\n", i);
544 return FALSE;
545 }
546 }
547
548 /* OK, all the range is held by the VDD. Commit the pages. */
549 Status = NtAllocateVirtualMemory(NtCurrentProcess(),
550 &Address,
551 0,
552 &RealSize,
553 MEM_COMMIT,
554 PAGE_READWRITE);
555 return NT_SUCCESS(Status);
556 }
557
558 BOOL
559 WINAPI
560 VDDFreeMem(IN HANDLE hVdd,
561 IN PVOID Address,
562 IN ULONG Size)
563 {
564 NTSTATUS Status;
565 PMEM_HOOK Hook;
566 ULONG i;
567 ULONG FirstPage = (ULONG_PTR)PHYS_TO_REAL(Address) >> 12;
568 ULONG LastPage = ((ULONG_PTR)PHYS_TO_REAL(Address) + Size - 1) >> 12;
569 SIZE_T RealSize = (LastPage - FirstPage + 1) * PAGE_SIZE;
570
571 /* Check validity of the VDD handle */
572 if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
573 {
574 SetLastError(ERROR_INVALID_PARAMETER);
575 return FALSE;
576 }
577
578 if (Size == 0) return FALSE;
579
580 /* Fixup the address */
581 Address = (PVOID)REAL_TO_PHYS(FirstPage * PAGE_SIZE);
582
583 /* Be sure that all the region is held by the VDD */
584 for (i = FirstPage; i <= LastPage; i++)
585 {
586 Hook = PageTable[i];
587 if (Hook == NULL) return FALSE;
588
589 if (Hook->hVdd != hVdd)
590 {
591 DPRINT1("VDDFreeMem: Page %u owned by someone else.\n", i);
592 return FALSE;
593 }
594 }
595
596 /* OK, all the range is held by the VDD. Decommit the pages. */
597 Status = NtFreeVirtualMemory(NtCurrentProcess(),
598 &Address,
599 &RealSize,
600 MEM_DECOMMIT);
601 return NT_SUCCESS(Status);
602 }
603
604 BOOL
605 WINAPI
606 VDDIncludeMem(IN HANDLE hVdd,
607 IN PVOID Address,
608 IN ULONG Size)
609 {
610 // FIXME
611 UNIMPLEMENTED;
612 return FALSE;
613 }
614
615 BOOL
616 WINAPI
617 VDDExcludeMem(IN HANDLE hVdd,
618 IN PVOID Address,
619 IN ULONG Size)
620 {
621 // FIXME
622 UNIMPLEMENTED;
623 return FALSE;
624 }
625
626
627
628 BOOLEAN
629 MemInitialize(VOID)
630 {
631 NTSTATUS Status;
632 SIZE_T MemorySize = MAX_ADDRESS; // See: kernel32/client/vdm.c!BaseGetVdmConfigInfo
633
634 InitializeListHead(&HookList);
635
636 #ifndef STANDALONE
637
638 /*
639 * The reserved region starts from the very first page.
640 * We need to commit the reserved first 16 MB virtual address.
641 *
642 * NOTE: NULL has another signification for NtAllocateVirtualMemory.
643 */
644 BaseAddress = (PVOID)1;
645
646 /*
647 * Since to get NULL, we allocated from 0x1, account for this.
648 * See also: kernel32/client/proc.c!CreateProcessInternalW
649 */
650 MemorySize -= 1;
651
652 #else
653
654 /* Allocate it anywhere */
655 BaseAddress = NULL;
656
657 #endif
658
659 Status = NtAllocateVirtualMemory(NtCurrentProcess(),
660 &BaseAddress,
661 0,
662 &MemorySize,
663 #ifndef STANDALONE
664 MEM_COMMIT,
665 #else
666 MEM_RESERVE | MEM_COMMIT,
667 #endif
668 PAGE_EXECUTE_READWRITE);
669 if (!NT_SUCCESS(Status))
670 {
671 wprintf(L"FATAL: Failed to commit VDM memory, Status 0x%08lx\n", Status);
672 return FALSE;
673 }
674
675 #ifndef STANDALONE
676 ASSERT(BaseAddress == NULL);
677 #endif
678
679 /*
680 * For diagnostics purposes, we fill the memory with INT 0x03 codes
681 * so that if a program wants to execute random code in memory, we can
682 * retrieve the exact CS:IP where the problem happens.
683 */
684 RtlFillMemory(BaseAddress, MAX_ADDRESS, 0xCC);
685 return TRUE;
686 }
687
688 VOID
689 MemCleanup(VOID)
690 {
691 NTSTATUS Status;
692 SIZE_T MemorySize = MAX_ADDRESS;
693 PLIST_ENTRY Pointer;
694
695 while (!IsListEmpty(&HookList))
696 {
697 Pointer = RemoveHeadList(&HookList);
698 RtlFreeHeap(RtlGetProcessHeap(), 0, CONTAINING_RECORD(Pointer, MEM_HOOK, Entry));
699 }
700
701 /* Decommit the VDM memory */
702 Status = NtFreeVirtualMemory(NtCurrentProcess(),
703 &BaseAddress,
704 &MemorySize,
705 #ifndef STANDALONE
706 MEM_DECOMMIT
707 #else
708 MEM_RELEASE
709 #endif
710 );
711 if (!NT_SUCCESS(Status))
712 {
713 DPRINT1("NTVDM: Failed to decommit VDM memory, Status 0x%08lx\n", Status);
714 }
715 }
716
717 /* EOF */