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