[NTVDM] Fix PCH w.r.t. using the debug routines. The PCH use here in itself could...
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dos32krnl / memory.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/memory.c
5 * PURPOSE: DOS32 Memory Manager
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #include "ntvdm.h"
13
14 #define NDEBUG
15 #include <debug.h>
16
17 #include "emulator.h"
18
19 #include "bios/umamgr.h" // HACK until we correctly call XMS services for UMBs.
20
21 #include "dos.h"
22 #include "dos/dem.h"
23 #include "memory.h"
24 #include "process.h"
25 #include "himem.h"
26
27 // FIXME: Should be dynamically initialized!
28 #define FIRST_MCB_SEGMENT (SYSTEM_ENV_BLOCK + 0x200) // old value: 0x1000
29 #define USER_MEMORY_SIZE (0x9FFE - FIRST_MCB_SEGMENT)
30
31 /*
32 * Activate this line if you want run-time DOS memory arena integrity validation
33 * (useful to know whether this is an application, or DOS kernel itself, which
34 * messes up the DOS memory arena).
35 */
36 // #define DBG_MEMORY
37
38 /* PRIVATE VARIABLES **********************************************************/
39
40 /* PUBLIC VARIABLES ***********************************************************/
41
42 /* PRIVATE FUNCTIONS **********************************************************/
43
44 static inline BOOLEAN ValidateMcb(PDOS_MCB Mcb)
45 {
46 return (Mcb->BlockType == 'M' || Mcb->BlockType == 'Z');
47 }
48
49 /*
50 * This is a helper function to help us detecting
51 * when the DOS arena starts to become corrupted.
52 */
53 #ifdef DBG_MEMORY
54 static VOID DosMemValidate(VOID)
55 {
56 WORD PrevSegment, Segment = SysVars->FirstMcb;
57 PDOS_MCB CurrentMcb;
58
59 PrevSegment = Segment;
60 while (TRUE)
61 {
62 /* Get a pointer to the MCB */
63 CurrentMcb = SEGMENT_TO_MCB(Segment);
64
65 /* Make sure it's valid */
66 if (!ValidateMcb(CurrentMcb))
67 {
68 DPRINT1("The DOS memory arena is corrupted! (CurrentMcb = 0x%04X; PreviousMcb = 0x%04X)\n", Segment, PrevSegment);
69 return;
70 }
71
72 PrevSegment = Segment;
73
74 /* If this was the last MCB in the chain, quit */
75 if (CurrentMcb->BlockType == 'Z') return;
76
77 /* Otherwise, update the segment and continue */
78 Segment += CurrentMcb->Size + 1;
79 }
80 }
81 #else
82 #define DosMemValidate()
83 #endif
84
85 static VOID DosCombineFreeBlocks(WORD StartBlock)
86 {
87 /* NOTE: This function is always called with valid MCB blocks */
88
89 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
90
91 /* If the block is not free, quit */
92 if (CurrentMcb->OwnerPsp != 0) return;
93
94 /*
95 * Loop while the current block is not the last one. It can happen
96 * that the block is not the last one at the beginning, but becomes
97 * the last one at the end of the process. This happens in the case
98 * where its next following blocks are free but not combined yet,
99 * and they are terminated by a free last block. During the process
100 * all the blocks are combined together and we end up in the situation
101 * where the current (free) block is followed by the last (free) block.
102 * At the last step of the algorithm the current block becomes the
103 * last one.
104 */
105 while (CurrentMcb->BlockType != 'Z')
106 {
107 /* Get a pointer to the next MCB */
108 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
109
110 /* Make sure it's valid */
111 if (!ValidateMcb(NextMcb))
112 {
113 DPRINT1("The DOS memory arena is corrupted!\n");
114 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
115 return;
116 }
117
118 /* Check if the next MCB is free */
119 if (NextMcb->OwnerPsp == 0)
120 {
121 /* Combine them */
122 CurrentMcb->Size += NextMcb->Size + 1;
123 CurrentMcb->BlockType = NextMcb->BlockType;
124 NextMcb->BlockType = 'I';
125 }
126 else
127 {
128 /* No more adjoining free blocks */
129 break;
130 }
131 }
132 }
133
134 /* PUBLIC FUNCTIONS ***********************************************************/
135
136 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
137 {
138 WORD Result = 0, Segment = SysVars->FirstMcb, MaxSize = 0;
139 PDOS_MCB CurrentMcb;
140 BOOLEAN SearchUmb = FALSE;
141
142 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size);
143
144 DosMemValidate();
145
146 if (SysVars->UmbLinked && SysVars->UmbChainStart != 0xFFFF &&
147 (Sda->AllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
148 {
149 /* Search UMB first */
150 Segment = SysVars->UmbChainStart;
151 SearchUmb = TRUE;
152 }
153
154 while (TRUE)
155 {
156 /* Get a pointer to the MCB */
157 CurrentMcb = SEGMENT_TO_MCB(Segment);
158
159 /* Make sure it's valid */
160 if (!ValidateMcb(CurrentMcb))
161 {
162 DPRINT1("The DOS memory arena is corrupted!\n");
163 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
164 return 0;
165 }
166
167 /* Only check free blocks */
168 if (CurrentMcb->OwnerPsp != 0) goto Next;
169
170 /* Combine this free block with adjoining free blocks */
171 DosCombineFreeBlocks(Segment);
172
173 /* Update the maximum block size */
174 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
175
176 /* Check if this block is big enough */
177 if (CurrentMcb->Size < Size) goto Next;
178
179 switch (Sda->AllocStrategy & ~(DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
180 {
181 case DOS_ALLOC_FIRST_FIT:
182 {
183 /* For first fit, stop immediately */
184 Result = Segment;
185 goto Done;
186 }
187
188 case DOS_ALLOC_BEST_FIT:
189 {
190 /* For best fit, update the smallest block found so far */
191 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
192 {
193 Result = Segment;
194 }
195
196 break;
197 }
198
199 case DOS_ALLOC_LAST_FIT:
200 {
201 /* For last fit, make the current block the result, but keep searching */
202 Result = Segment;
203 break;
204 }
205 }
206
207 Next:
208 /* If this was the last MCB in the chain, quit */
209 if (CurrentMcb->BlockType == 'Z')
210 {
211 /* Check if nothing was found while searching through UMBs */
212 if ((Result == 0) && SearchUmb && (Sda->AllocStrategy & DOS_ALLOC_HIGH_LOW))
213 {
214 /* Search low memory */
215 Segment = SysVars->FirstMcb;
216 SearchUmb = FALSE;
217 continue;
218 }
219
220 break;
221 }
222
223 /* Otherwise, update the segment and continue */
224 Segment += CurrentMcb->Size + 1;
225 }
226
227 Done:
228 DosMemValidate();
229
230 /* If we didn't find a free block, bail out */
231 if (Result == 0)
232 {
233 DPRINT("DosAllocateMemory FAILED. Maximum available: 0x%04X\n", MaxSize);
234 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
235 if (MaxAvailable) *MaxAvailable = MaxSize;
236 return 0;
237 }
238
239 /* Get a pointer to the MCB */
240 CurrentMcb = SEGMENT_TO_MCB(Result);
241
242 /* Check if the block is larger than requested */
243 if (CurrentMcb->Size > Size)
244 {
245 /* It is, split it into two blocks */
246 if ((Sda->AllocStrategy & ~(DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)) != DOS_ALLOC_LAST_FIT)
247 {
248 PDOS_MCB NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
249
250 /* Initialize the new MCB structure */
251 NextMcb->BlockType = CurrentMcb->BlockType;
252 NextMcb->Size = CurrentMcb->Size - Size - 1;
253 NextMcb->OwnerPsp = 0;
254
255 /* Update the current block */
256 CurrentMcb->BlockType = 'M';
257 CurrentMcb->Size = Size;
258 }
259 else
260 {
261 /* Save the location of the current MCB */
262 PDOS_MCB PreviousMcb = CurrentMcb;
263
264 /* Move the current MCB higher */
265 Result += CurrentMcb->Size - Size;
266 CurrentMcb = SEGMENT_TO_MCB(Result);
267
268 /* Initialize the new MCB structure */
269 CurrentMcb->BlockType = PreviousMcb->BlockType;
270 CurrentMcb->Size = Size;
271 CurrentMcb->OwnerPsp = 0;
272
273 /* Update the previous block */
274 PreviousMcb->BlockType = 'M';
275 PreviousMcb->Size -= Size + 1;
276 }
277 }
278
279 /* Take ownership of the block */
280 CurrentMcb->OwnerPsp = Sda->CurrentPsp;
281 RtlCopyMemory(CurrentMcb->Name, SEGMENT_TO_MCB(Sda->CurrentPsp - 1)->Name, sizeof(CurrentMcb->Name));
282
283 DosMemValidate();
284
285 /* Return the segment of the data portion of the block */
286 return Result + 1;
287 }
288
289 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
290 {
291 BOOLEAN Success = TRUE;
292 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
293 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
294
295 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
296 BlockData, NewSize);
297
298 DosMemValidate();
299
300 /* Make sure this is a valid and allocated block */
301 if (BlockData == 0 || !ValidateMcb(Mcb) || Mcb->OwnerPsp == 0)
302 {
303 Sda->LastErrorCode = ERROR_INVALID_BLOCK;
304 Success = FALSE;
305 goto Done;
306 }
307
308 ReturnSize = Mcb->Size;
309
310 /* Check if we need to expand or contract the block */
311 if (NewSize > Mcb->Size)
312 {
313 /* We can't expand the last block */
314 if (Mcb->BlockType == 'Z')
315 {
316 DPRINT("Cannot expand memory block 0x%04X: this is the last block (size 0x%04X)!\n", Segment, Mcb->Size);
317 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
318 Success = FALSE;
319 goto Done;
320 }
321
322 /* Get the pointer and segment of the next MCB */
323 NextSegment = Segment + Mcb->Size + 1;
324 NextMcb = SEGMENT_TO_MCB(NextSegment);
325
326 /* Make sure it's valid */
327 if (!ValidateMcb(NextMcb))
328 {
329 DPRINT1("The DOS memory arena is corrupted!\n");
330 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
331 return FALSE;
332 }
333
334 /* Make sure the next segment is free */
335 if (NextMcb->OwnerPsp != 0)
336 {
337 DPRINT("Cannot expand memory block 0x%04X: next segment is not free!\n", Segment);
338 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
339 Success = FALSE;
340 goto Done;
341 }
342
343 /* Combine this free block with adjoining free blocks */
344 DosCombineFreeBlocks(NextSegment);
345
346 /* Set the maximum possible size of the block */
347 ReturnSize += NextMcb->Size + 1;
348
349 if (ReturnSize < NewSize)
350 {
351 DPRINT("Cannot expand memory block 0x%04X: insufficient free segments available!\n", Segment);
352 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
353 Success = FALSE;
354 goto Done;
355 }
356
357 /* Maximize the current block */
358 Mcb->Size = ReturnSize;
359 Mcb->BlockType = NextMcb->BlockType;
360
361 /* Invalidate the next block */
362 NextMcb->BlockType = 'I';
363
364 /* Check if the block is larger than requested */
365 if (Mcb->Size > NewSize)
366 {
367 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
368 Mcb->Size, NewSize);
369
370 /* It is, split it into two blocks */
371 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
372
373 /* Initialize the new MCB structure */
374 NextMcb->BlockType = Mcb->BlockType;
375 NextMcb->Size = Mcb->Size - NewSize - 1;
376 NextMcb->OwnerPsp = 0;
377
378 /* Update the current block */
379 Mcb->BlockType = 'M';
380 Mcb->Size = NewSize;
381 }
382 }
383 else if (NewSize < Mcb->Size)
384 {
385 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
386 Mcb->Size, NewSize);
387
388 /* Just split the block */
389 NextSegment = Segment + NewSize + 1;
390 NextMcb = SEGMENT_TO_MCB(NextSegment);
391 NextMcb->BlockType = Mcb->BlockType;
392 NextMcb->Size = Mcb->Size - NewSize - 1;
393 NextMcb->OwnerPsp = 0;
394
395 /* Update the MCB */
396 Mcb->BlockType = 'M';
397 Mcb->Size = NewSize;
398
399 /* Combine this free block with adjoining free blocks */
400 DosCombineFreeBlocks(NextSegment);
401 }
402
403 Done:
404 /* Check if the operation failed */
405 if (!Success)
406 {
407 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n", ReturnSize);
408
409 /* Return the maximum possible size */
410 if (MaxAvailable) *MaxAvailable = ReturnSize;
411 }
412
413 DosMemValidate();
414
415 return Success;
416 }
417
418 BOOLEAN DosFreeMemory(WORD BlockData)
419 {
420 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
421
422 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
423
424 if (BlockData == 0)
425 {
426 Sda->LastErrorCode = ERROR_INVALID_BLOCK;
427 return FALSE;
428 }
429
430 DosMemValidate();
431
432 /* Make sure the MCB is valid */
433 if (!ValidateMcb(Mcb))
434 {
435 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
436 Sda->LastErrorCode = ERROR_INVALID_BLOCK;
437 return FALSE;
438 }
439
440 /* Mark the block as free */
441 Mcb->OwnerPsp = 0;
442
443 return TRUE;
444 }
445
446 BOOLEAN DosLinkUmb(VOID)
447 {
448 DWORD Segment = SysVars->FirstMcb;
449 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
450
451 DPRINT("Linking UMB\n");
452
453 /* Check if UMBs are initialized and already linked */
454 if (SysVars->UmbChainStart == 0xFFFF) return FALSE;
455 if (SysVars->UmbLinked) return TRUE;
456
457 DosMemValidate();
458
459 /* Find the last block before the start of the UMB chain */
460 while (Segment < SysVars->UmbChainStart)
461 {
462 /* Get a pointer to the MCB */
463 Mcb = SEGMENT_TO_MCB(Segment);
464
465 /* Make sure it's valid */
466 if (!ValidateMcb(Mcb))
467 {
468 DPRINT1("The DOS memory arena is corrupted!\n");
469 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
470 return FALSE;
471 }
472
473 /* If this was the last MCB in the chain, quit */
474 if (Mcb->BlockType == 'Z') break;
475
476 /* Otherwise, update the segment and continue */
477 Segment += Mcb->Size + 1;
478 }
479
480 /* Make sure it's valid */
481 if (Mcb->BlockType != 'Z') return FALSE;
482
483 /* Connect the MCB with the UMB chain */
484 Mcb->BlockType = 'M';
485
486 DosMemValidate();
487
488 SysVars->UmbLinked = TRUE;
489 return TRUE;
490 }
491
492 BOOLEAN DosUnlinkUmb(VOID)
493 {
494 DWORD Segment = SysVars->FirstMcb;
495 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
496
497 DPRINT("Unlinking UMB\n");
498
499 /* Check if UMBs are initialized and already unlinked */
500 if (SysVars->UmbChainStart == 0xFFFF) return FALSE;
501 if (!SysVars->UmbLinked) return TRUE;
502
503 DosMemValidate();
504
505 /* Find the last block before the start of the UMB chain */
506 while (Segment < SysVars->UmbChainStart)
507 {
508 /* Get a pointer to the MCB */
509 Mcb = SEGMENT_TO_MCB(Segment);
510
511 /* Make sure it's valid */
512 if (!ValidateMcb(Mcb))
513 {
514 DPRINT1("The DOS memory arena is corrupted!\n");
515 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
516 return FALSE;
517 }
518
519 /* Advance to the next MCB */
520 Segment += Mcb->Size + 1;
521 }
522
523 /* Mark the MCB as the last MCB */
524 Mcb->BlockType = 'Z';
525
526 DosMemValidate();
527
528 SysVars->UmbLinked = FALSE;
529 return TRUE;
530 }
531
532 VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
533 {
534 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
535 Mcb->OwnerPsp = NewOwner;
536 }
537
538 /*
539 * Some information about DOS UMBs:
540 * http://textfiles.com/virus/datut010.txt
541 * http://www.asmcommunity.net/forums/topic/?id=30884
542 */
543
544 WORD DosGetPreviousUmb(WORD UmbSegment)
545 {
546 PDOS_MCB CurrentMcb;
547 WORD Segment, PrevSegment = 0; // FIXME: or use UmbChainStart ??
548
549 if (SysVars->UmbChainStart == 0xFFFF)
550 return 0;
551
552 /* Start scanning the UMB chain */
553 Segment = SysVars->UmbChainStart;
554 while (TRUE)
555 {
556 /* Get a pointer to the MCB */
557 CurrentMcb = SEGMENT_TO_MCB(Segment);
558
559 /* Make sure it's valid */
560 if (!ValidateMcb(CurrentMcb))
561 {
562 DPRINT1("The UMB DOS memory arena is corrupted!\n");
563 Sda->LastErrorCode = ERROR_ARENA_TRASHED;
564 return 0;
565 }
566
567 /* We went over the UMB segment, quit */
568 if (Segment >= UmbSegment) break;
569
570 PrevSegment = Segment;
571
572 /* If this was the last MCB in the chain, quit */
573 if (CurrentMcb->BlockType == 'Z') break;
574
575 /* Otherwise, update the segment and continue */
576 Segment += CurrentMcb->Size + 1;
577 }
578
579 return PrevSegment;
580 }
581
582 VOID DosInitializeUmb(VOID)
583 {
584 BOOLEAN Result;
585 USHORT UmbSegment = 0x0000, PrevSegment;
586 USHORT Size;
587 PDOS_MCB Mcb, PrevMcb;
588
589 ASSERT(SysVars->UmbChainStart == 0xFFFF);
590
591 // SysVars->UmbLinked = FALSE;
592
593 /* Try to allocate all the UMBs */
594 while (TRUE)
595 {
596 /* Find the maximum amount of memory that can be allocated */
597 Size = 0xFFFF;
598 Result = UmaDescReserve(&UmbSegment, &Size);
599
600 /* If we are out of UMBs, bail out */
601 if (!Result && Size == 0) // XMS_STATUS_OUT_OF_UMBS
602 break;
603
604 /* We should not have succeeded! */
605 ASSERT(!Result);
606
607 /* 'Size' now contains the size of the biggest UMB block. Request it. */
608 Result = UmaDescReserve(&UmbSegment, &Size);
609 ASSERT(Result); // XMS_STATUS_SUCCESS
610
611 /* If this is our first UMB block, initialize the UMB chain */
612 if (SysVars->UmbChainStart == 0xFFFF)
613 {
614 /* Initialize the link MCB to the UMB area */
615 // NOTE: We use the fact that UmbChainStart is still == 0xFFFF
616 // so that we initialize this block from 9FFF:0000 up to FFFF:000F.
617 // It will be splitted as needed just below.
618 Mcb = SEGMENT_TO_MCB(SysVars->FirstMcb + USER_MEMORY_SIZE + 1); // '+1': Readjust the fact that USER_MEMORY_SIZE is based using 0x9FFE instead of 0x9FFF
619 Mcb->BlockType = 'Z'; // At the moment it is really the last block
620 Mcb->Size = (SysVars->UmbChainStart /* UmbSegment */ - SysVars->FirstMcb - USER_MEMORY_SIZE - 2) + 1;
621 Mcb->OwnerPsp = SYSTEM_PSP;
622 RtlCopyMemory(Mcb->Name, "SC ", sizeof("SC ")-1);
623
624 #if 0 // Keep here for reference; this will be deleted as soon as it becomes unneeded.
625 /* Initialize the UMB area */
626 Mcb = SEGMENT_TO_MCB(SysVars->UmbChainStart);
627 Mcb->Size = UMB_END_SEGMENT - SysVars->UmbChainStart;
628 #endif
629
630 // FIXME: We should adjust the size of the previous block!!
631
632 /* Initialize the start of the UMB chain */
633 SysVars->UmbChainStart = SysVars->FirstMcb + USER_MEMORY_SIZE + 1;
634 }
635
636 /* Split the block */
637
638 /* Get the previous block */
639 PrevSegment = DosGetPreviousUmb(UmbSegment);
640 PrevMcb = SEGMENT_TO_MCB(PrevSegment);
641
642 /* Initialize the next block */
643 Mcb = SEGMENT_TO_MCB(UmbSegment + /*Mcb->Size*/(Size - 1) + 0);
644 // Mcb->BlockType = 'Z'; // FIXME: What if this block happens to be the last one??
645 Mcb->BlockType = PrevMcb->BlockType;
646 Mcb->Size = PrevMcb->Size - (UmbSegment + Size - PrevSegment) + 1;
647 Mcb->OwnerPsp = PrevMcb->OwnerPsp;
648 RtlCopyMemory(Mcb->Name, PrevMcb->Name, sizeof(PrevMcb->Name));
649
650 /* The previous block is not the latest one anymore */
651 PrevMcb->BlockType = 'M';
652 PrevMcb->Size = UmbSegment - PrevSegment - 1;
653
654 /* Initialize the new UMB block */
655 Mcb = SEGMENT_TO_MCB(UmbSegment);
656 Mcb->BlockType = 'M'; // 'Z' // FIXME: What if this block happens to be the last one??
657 Mcb->Size = Size - 1 - 1; // minus 2 because we need to have one arena at the beginning and one at the end.
658 Mcb->OwnerPsp = 0;
659 // FIXME: Which MCB name should we use? I need to explore more the docs!
660 RtlCopyMemory(Mcb->Name, "UMB ", sizeof("UMB ")-1);
661 // RtlCopyMemory(Mcb->Name, "SM ", sizeof("SM ")-1);
662 }
663 }
664
665 VOID DosInitializeMemory(VOID)
666 {
667 PDOS_MCB Mcb;
668
669 /* Set the initial allocation strategy to "best fit" */
670 Sda->AllocStrategy = DOS_ALLOC_BEST_FIT;
671
672 /* Initialize conventional memory; we don't have UMBs yet */
673 SysVars->FirstMcb = FIRST_MCB_SEGMENT; // The Arena Head
674 SysVars->UmbLinked = FALSE;
675 SysVars->UmbChainStart = 0xFFFF;
676
677 Mcb = SEGMENT_TO_MCB(SysVars->FirstMcb);
678
679 /* Initialize the MCB */
680 Mcb->BlockType = 'Z';
681 Mcb->Size = USER_MEMORY_SIZE;
682 Mcb->OwnerPsp = 0;
683 }
684
685 /* EOF */