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