[NTVDM]
[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: dos/dos32krnl/memory.c
5 * PURPOSE: DOS32 Memory Manager
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 #include "dos.h"
18 #include "dos/dem.h"
19
20 /* PUBLIC VARIABLES ***********************************************************/
21
22 BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
23 BOOLEAN DosUmbLinked = FALSE;
24
25 /* PRIVATE FUNCTIONS **********************************************************/
26
27 static VOID DosCombineFreeBlocks(WORD StartBlock)
28 {
29 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
30
31 /* If this is the last block or it's not free, quit */
32 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
33
34 while (TRUE)
35 {
36 /* Get a pointer to the next MCB */
37 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
38
39 /* Check if the next MCB is free */
40 if (NextMcb->OwnerPsp == 0)
41 {
42 /* Combine them */
43 CurrentMcb->Size += NextMcb->Size + 1;
44 CurrentMcb->BlockType = NextMcb->BlockType;
45 NextMcb->BlockType = 'I';
46 }
47 else
48 {
49 /* No more adjoining free blocks */
50 break;
51 }
52 }
53 }
54
55 /* PUBLIC FUNCTIONS ***********************************************************/
56
57 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
58 {
59 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
60 PDOS_MCB CurrentMcb;
61 BOOLEAN SearchUmb = FALSE;
62
63 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size);
64
65 if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
66 {
67 /* Search UMB first */
68 Segment = UMB_START_SEGMENT;
69 SearchUmb = TRUE;
70 }
71
72 while (TRUE)
73 {
74 /* Get a pointer to the MCB */
75 CurrentMcb = SEGMENT_TO_MCB(Segment);
76
77 /* Make sure it's valid */
78 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
79 {
80 DPRINT("The DOS memory arena is corrupted!\n");
81 DosLastError = ERROR_ARENA_TRASHED;
82 return 0;
83 }
84
85 /* Only check free blocks */
86 if (CurrentMcb->OwnerPsp != 0) goto Next;
87
88 /* Combine this free block with adjoining free blocks */
89 DosCombineFreeBlocks(Segment);
90
91 /* Update the maximum block size */
92 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
93
94 /* Check if this block is big enough */
95 if (CurrentMcb->Size < Size) goto Next;
96
97 switch (DosAllocStrategy & 0x3F)
98 {
99 case DOS_ALLOC_FIRST_FIT:
100 {
101 /* For first fit, stop immediately */
102 Result = Segment;
103 goto Done;
104 }
105
106 case DOS_ALLOC_BEST_FIT:
107 {
108 /* For best fit, update the smallest block found so far */
109 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
110 {
111 Result = Segment;
112 }
113
114 break;
115 }
116
117 case DOS_ALLOC_LAST_FIT:
118 {
119 /* For last fit, make the current block the result, but keep searching */
120 Result = Segment;
121 break;
122 }
123 }
124
125 Next:
126 /* If this was the last MCB in the chain, quit */
127 if (CurrentMcb->BlockType == 'Z')
128 {
129 /* Check if nothing was found while searching through UMBs */
130 if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW))
131 {
132 /* Search low memory */
133 Segment = FIRST_MCB_SEGMENT;
134 SearchUmb = FALSE;
135 continue;
136 }
137
138 break;
139 }
140
141 /* Otherwise, update the segment and continue */
142 Segment += CurrentMcb->Size + 1;
143 }
144
145 Done:
146
147 /* If we didn't find a free block, return 0 */
148 if (Result == 0)
149 {
150 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
151 if (MaxAvailable) *MaxAvailable = MaxSize;
152 return 0;
153 }
154
155 /* Get a pointer to the MCB */
156 CurrentMcb = SEGMENT_TO_MCB(Result);
157
158 /* Check if the block is larger than requested */
159 if (CurrentMcb->Size > Size)
160 {
161 /* It is, split it into two blocks */
162 if ((DosAllocStrategy & 0x3F) != DOS_ALLOC_LAST_FIT)
163 {
164 PDOS_MCB NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
165
166 /* Initialize the new MCB structure */
167 NextMcb->BlockType = CurrentMcb->BlockType;
168 NextMcb->Size = CurrentMcb->Size - Size - 1;
169 NextMcb->OwnerPsp = 0;
170
171 /* Update the current block */
172 CurrentMcb->BlockType = 'M';
173 CurrentMcb->Size = Size;
174 }
175 else
176 {
177 /* Save the location of the current MCB */
178 PDOS_MCB PreviousMcb = CurrentMcb;
179
180 /* Move the current MCB higher */
181 Result += CurrentMcb->Size - Size;
182 CurrentMcb = SEGMENT_TO_MCB(Result);
183
184 /* Initialize the new MCB structure */
185 CurrentMcb->BlockType = PreviousMcb->BlockType;
186 CurrentMcb->Size = Size;
187 CurrentMcb->OwnerPsp = 0;
188
189 /* Update the previous block */
190 PreviousMcb->BlockType = 'M';
191 PreviousMcb->Size -= Size + 1;
192 }
193 }
194
195 /* Take ownership of the block */
196 CurrentMcb->OwnerPsp = CurrentPsp;
197
198 /* Return the segment of the data portion of the block */
199 return Result + 1;
200 }
201
202 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
203 {
204 BOOLEAN Success = TRUE;
205 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
206 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
207
208 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
209 BlockData,
210 NewSize);
211
212 /* Make sure this is a valid, allocated block */
213 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
214 {
215 Success = FALSE;
216 DosLastError = ERROR_INVALID_HANDLE;
217 goto Done;
218 }
219
220 ReturnSize = Mcb->Size;
221
222 /* Check if we need to expand or contract the block */
223 if (NewSize > Mcb->Size)
224 {
225 /* We can't expand the last block */
226 if (Mcb->BlockType != 'M')
227 {
228 Success = FALSE;
229 goto Done;
230 }
231
232 /* Get the pointer and segment of the next MCB */
233 NextSegment = Segment + Mcb->Size + 1;
234 NextMcb = SEGMENT_TO_MCB(NextSegment);
235
236 /* Make sure the next segment is free */
237 if (NextMcb->OwnerPsp != 0)
238 {
239 DPRINT("Cannot expand memory block: next segment is not free!\n");
240 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
241 Success = FALSE;
242 goto Done;
243 }
244
245 /* Combine this free block with adjoining free blocks */
246 DosCombineFreeBlocks(NextSegment);
247
248 /* Set the maximum possible size of the block */
249 ReturnSize += NextMcb->Size + 1;
250
251 if (ReturnSize < NewSize)
252 {
253 DPRINT("Cannot expand memory block: insufficient free segments available!\n");
254 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
255 Success = FALSE;
256 goto Done;
257 }
258
259 /* Maximize the current block */
260 Mcb->Size = ReturnSize;
261 Mcb->BlockType = NextMcb->BlockType;
262
263 /* Invalidate the next block */
264 NextMcb->BlockType = 'I';
265
266 /* Check if the block is larger than requested */
267 if (Mcb->Size > NewSize)
268 {
269 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
270 Mcb->Size,
271 NewSize);
272
273 /* It is, split it into two blocks */
274 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
275
276 /* Initialize the new MCB structure */
277 NextMcb->BlockType = Mcb->BlockType;
278 NextMcb->Size = Mcb->Size - NewSize - 1;
279 NextMcb->OwnerPsp = 0;
280
281 /* Update the current block */
282 Mcb->BlockType = 'M';
283 Mcb->Size = NewSize;
284 }
285 }
286 else if (NewSize < Mcb->Size)
287 {
288 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
289 Mcb->Size,
290 NewSize);
291
292 /* Just split the block */
293 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
294 NextMcb->BlockType = Mcb->BlockType;
295 NextMcb->Size = Mcb->Size - NewSize - 1;
296 NextMcb->OwnerPsp = 0;
297
298 /* Update the MCB */
299 Mcb->BlockType = 'M';
300 Mcb->Size = NewSize;
301 }
302
303 Done:
304 /* Check if the operation failed */
305 if (!Success)
306 {
307 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
308 ReturnSize);
309
310 /* Return the maximum possible size */
311 if (MaxAvailable) *MaxAvailable = ReturnSize;
312 }
313
314 return Success;
315 }
316
317 BOOLEAN DosFreeMemory(WORD BlockData)
318 {
319 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
320
321 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
322
323 /* Make sure the MCB is valid */
324 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
325 {
326 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
327 return FALSE;
328 }
329
330 /* Mark the block as free */
331 Mcb->OwnerPsp = 0;
332
333 return TRUE;
334 }
335
336 BOOLEAN DosLinkUmb(VOID)
337 {
338 DWORD Segment = FIRST_MCB_SEGMENT;
339 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
340
341 DPRINT("Linking UMB\n");
342
343 /* Check if UMBs are already linked */
344 if (DosUmbLinked) return FALSE;
345
346 /* Find the last block */
347 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
348 {
349 Segment += Mcb->Size + 1;
350 Mcb = SEGMENT_TO_MCB(Segment);
351 }
352
353 /* Make sure it's valid */
354 if (Mcb->BlockType != 'Z') return FALSE;
355
356 /* Connect the MCB with the UMB chain */
357 Mcb->BlockType = 'M';
358
359 DosUmbLinked = TRUE;
360 return TRUE;
361 }
362
363 BOOLEAN DosUnlinkUmb(VOID)
364 {
365 DWORD Segment = FIRST_MCB_SEGMENT;
366 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
367
368 DPRINT("Unlinking UMB\n");
369
370 /* Check if UMBs are already unlinked */
371 if (!DosUmbLinked) return FALSE;
372
373 /* Find the block preceding the MCB that links it with the UMB chain */
374 while (Segment <= 0xFFFF)
375 {
376 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
377 {
378 /* This is the last non-UMB segment */
379 break;
380 }
381
382 /* Advance to the next MCB */
383 Segment += Mcb->Size + 1;
384 Mcb = SEGMENT_TO_MCB(Segment);
385 }
386
387 /* Mark the MCB as the last MCB */
388 Mcb->BlockType = 'Z';
389
390 DosUmbLinked = FALSE;
391 return TRUE;
392 }
393
394 VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
395 {
396 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
397
398 /* Just set the owner */
399 Mcb->OwnerPsp = NewOwner;
400 }
401