[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, NextMcb;
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 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
163
164 /* Initialize the new MCB structure */
165 NextMcb->BlockType = CurrentMcb->BlockType;
166 NextMcb->Size = CurrentMcb->Size - Size - 1;
167 NextMcb->OwnerPsp = 0;
168
169 /* Update the current block */
170 CurrentMcb->BlockType = 'M';
171 CurrentMcb->Size = Size;
172 }
173
174 /* Take ownership of the block */
175 CurrentMcb->OwnerPsp = CurrentPsp;
176
177 /* Return the segment of the data portion of the block */
178 return Result + 1;
179 }
180
181 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
182 {
183 BOOLEAN Success = TRUE;
184 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
185 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
186
187 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
188 BlockData,
189 NewSize);
190
191 /* Make sure this is a valid, allocated block */
192 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
193 {
194 Success = FALSE;
195 DosLastError = ERROR_INVALID_HANDLE;
196 goto Done;
197 }
198
199 ReturnSize = Mcb->Size;
200
201 /* Check if we need to expand or contract the block */
202 if (NewSize > Mcb->Size)
203 {
204 /* We can't expand the last block */
205 if (Mcb->BlockType != 'M')
206 {
207 Success = FALSE;
208 goto Done;
209 }
210
211 /* Get the pointer and segment of the next MCB */
212 NextSegment = Segment + Mcb->Size + 1;
213 NextMcb = SEGMENT_TO_MCB(NextSegment);
214
215 /* Make sure the next segment is free */
216 if (NextMcb->OwnerPsp != 0)
217 {
218 DPRINT("Cannot expand memory block: next segment is not free!\n");
219 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
220 Success = FALSE;
221 goto Done;
222 }
223
224 /* Combine this free block with adjoining free blocks */
225 DosCombineFreeBlocks(NextSegment);
226
227 /* Set the maximum possible size of the block */
228 ReturnSize += NextMcb->Size + 1;
229
230 if (ReturnSize < NewSize)
231 {
232 DPRINT("Cannot expand memory block: insufficient free segments available!\n");
233 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
234 Success = FALSE;
235 goto Done;
236 }
237
238 /* Maximize the current block */
239 Mcb->Size = ReturnSize;
240 Mcb->BlockType = NextMcb->BlockType;
241
242 /* Invalidate the next block */
243 NextMcb->BlockType = 'I';
244
245 /* Check if the block is larger than requested */
246 if (Mcb->Size > NewSize)
247 {
248 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
249 Mcb->Size,
250 NewSize);
251
252 /* It is, split it into two blocks */
253 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
254
255 /* Initialize the new MCB structure */
256 NextMcb->BlockType = Mcb->BlockType;
257 NextMcb->Size = Mcb->Size - NewSize - 1;
258 NextMcb->OwnerPsp = 0;
259
260 /* Update the current block */
261 Mcb->BlockType = 'M';
262 Mcb->Size = NewSize;
263 }
264 }
265 else if (NewSize < Mcb->Size)
266 {
267 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
268 Mcb->Size,
269 NewSize);
270
271 /* Just split the block */
272 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
273 NextMcb->BlockType = Mcb->BlockType;
274 NextMcb->Size = Mcb->Size - NewSize - 1;
275 NextMcb->OwnerPsp = 0;
276
277 /* Update the MCB */
278 Mcb->BlockType = 'M';
279 Mcb->Size = NewSize;
280 }
281
282 Done:
283 /* Check if the operation failed */
284 if (!Success)
285 {
286 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
287 ReturnSize);
288
289 /* Return the maximum possible size */
290 if (MaxAvailable) *MaxAvailable = ReturnSize;
291 }
292
293 return Success;
294 }
295
296 BOOLEAN DosFreeMemory(WORD BlockData)
297 {
298 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
299
300 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
301
302 /* Make sure the MCB is valid */
303 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
304 {
305 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
306 return FALSE;
307 }
308
309 /* Mark the block as free */
310 Mcb->OwnerPsp = 0;
311
312 return TRUE;
313 }
314
315 BOOLEAN DosLinkUmb(VOID)
316 {
317 DWORD Segment = FIRST_MCB_SEGMENT;
318 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
319
320 DPRINT("Linking UMB\n");
321
322 /* Check if UMBs are already linked */
323 if (DosUmbLinked) return FALSE;
324
325 /* Find the last block */
326 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
327 {
328 Segment += Mcb->Size + 1;
329 Mcb = SEGMENT_TO_MCB(Segment);
330 }
331
332 /* Make sure it's valid */
333 if (Mcb->BlockType != 'Z') return FALSE;
334
335 /* Connect the MCB with the UMB chain */
336 Mcb->BlockType = 'M';
337
338 DosUmbLinked = TRUE;
339 return TRUE;
340 }
341
342 BOOLEAN DosUnlinkUmb(VOID)
343 {
344 DWORD Segment = FIRST_MCB_SEGMENT;
345 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
346
347 DPRINT("Unlinking UMB\n");
348
349 /* Check if UMBs are already unlinked */
350 if (!DosUmbLinked) return FALSE;
351
352 /* Find the block preceding the MCB that links it with the UMB chain */
353 while (Segment <= 0xFFFF)
354 {
355 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
356 {
357 /* This is the last non-UMB segment */
358 break;
359 }
360
361 /* Advance to the next MCB */
362 Segment += Mcb->Size + 1;
363 Mcb = SEGMENT_TO_MCB(Segment);
364 }
365
366 /* Mark the MCB as the last MCB */
367 Mcb->BlockType = 'Z';
368
369 DosUmbLinked = FALSE;
370 return TRUE;
371 }
372
373 VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
374 {
375 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
376
377 /* Just set the owner */
378 Mcb->OwnerPsp = NewOwner;
379 }
380