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