f0539280d81b1ed92d22f3f48de3ae62d2d0c3bb
[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 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 && (Sda->AllocStrategy & (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 Sda->LastErrorCode = 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 (Sda->AllocStrategy & 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 && (Sda->AllocStrategy & 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 Sda->LastErrorCode = 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 ((Sda->AllocStrategy & 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 = Sda->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 (BlockData == 0
214 || (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
215 || Mcb->OwnerPsp == 0)
216 {
217 Success = FALSE;
218 Sda->LastErrorCode = ERROR_INVALID_HANDLE;
219 goto Done;
220 }
221
222 ReturnSize = Mcb->Size;
223
224 /* Check if we need to expand or contract the block */
225 if (NewSize > Mcb->Size)
226 {
227 /* We can't expand the last block */
228 if (Mcb->BlockType != 'M')
229 {
230 Success = FALSE;
231 goto Done;
232 }
233
234 /* Get the pointer and segment of the next MCB */
235 NextSegment = Segment + Mcb->Size + 1;
236 NextMcb = SEGMENT_TO_MCB(NextSegment);
237
238 /* Make sure the next segment is free */
239 if (NextMcb->OwnerPsp != 0)
240 {
241 DPRINT("Cannot expand memory block: next segment is not free!\n");
242 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
243 Success = FALSE;
244 goto Done;
245 }
246
247 /* Combine this free block with adjoining free blocks */
248 DosCombineFreeBlocks(NextSegment);
249
250 /* Set the maximum possible size of the block */
251 ReturnSize += NextMcb->Size + 1;
252
253 if (ReturnSize < NewSize)
254 {
255 DPRINT("Cannot expand memory block: insufficient free segments available!\n");
256 Sda->LastErrorCode = ERROR_NOT_ENOUGH_MEMORY;
257 Success = FALSE;
258 goto Done;
259 }
260
261 /* Maximize the current block */
262 Mcb->Size = ReturnSize;
263 Mcb->BlockType = NextMcb->BlockType;
264
265 /* Invalidate the next block */
266 NextMcb->BlockType = 'I';
267
268 /* Check if the block is larger than requested */
269 if (Mcb->Size > NewSize)
270 {
271 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
272 Mcb->Size,
273 NewSize);
274
275 /* It is, split it into two blocks */
276 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
277
278 /* Initialize the new MCB structure */
279 NextMcb->BlockType = Mcb->BlockType;
280 NextMcb->Size = Mcb->Size - NewSize - 1;
281 NextMcb->OwnerPsp = 0;
282
283 /* Update the current block */
284 Mcb->BlockType = 'M';
285 Mcb->Size = NewSize;
286 }
287 }
288 else if (NewSize < Mcb->Size)
289 {
290 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
291 Mcb->Size,
292 NewSize);
293
294 /* Just split the block */
295 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
296 NextMcb->BlockType = Mcb->BlockType;
297 NextMcb->Size = Mcb->Size - NewSize - 1;
298 NextMcb->OwnerPsp = 0;
299
300 /* Update the MCB */
301 Mcb->BlockType = 'M';
302 Mcb->Size = NewSize;
303 }
304
305 Done:
306 /* Check if the operation failed */
307 if (!Success)
308 {
309 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
310 ReturnSize);
311
312 /* Return the maximum possible size */
313 if (MaxAvailable) *MaxAvailable = ReturnSize;
314 }
315
316 return Success;
317 }
318
319 BOOLEAN DosFreeMemory(WORD BlockData)
320 {
321 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
322
323 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
324 if (BlockData == 0) return FALSE;
325
326 /* Make sure the MCB is valid */
327 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
328 {
329 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
330 return FALSE;
331 }
332
333 /* Mark the block as free */
334 Mcb->OwnerPsp = 0;
335
336 return TRUE;
337 }
338
339 BOOLEAN DosLinkUmb(VOID)
340 {
341 DWORD Segment = FIRST_MCB_SEGMENT;
342 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
343
344 DPRINT("Linking UMB\n");
345
346 /* Check if UMBs are already linked */
347 if (DosUmbLinked) return FALSE;
348
349 /* Find the last block */
350 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
351 {
352 Segment += Mcb->Size + 1;
353 Mcb = SEGMENT_TO_MCB(Segment);
354 }
355
356 /* Make sure it's valid */
357 if (Mcb->BlockType != 'Z') return FALSE;
358
359 /* Connect the MCB with the UMB chain */
360 Mcb->BlockType = 'M';
361
362 DosUmbLinked = TRUE;
363 return TRUE;
364 }
365
366 BOOLEAN DosUnlinkUmb(VOID)
367 {
368 DWORD Segment = FIRST_MCB_SEGMENT;
369 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
370
371 DPRINT("Unlinking UMB\n");
372
373 /* Check if UMBs are already unlinked */
374 if (!DosUmbLinked) return FALSE;
375
376 /* Find the block preceding the MCB that links it with the UMB chain */
377 while (Segment <= 0xFFFF)
378 {
379 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
380 {
381 /* This is the last non-UMB segment */
382 break;
383 }
384
385 /* Advance to the next MCB */
386 Segment += Mcb->Size + 1;
387 Mcb = SEGMENT_TO_MCB(Segment);
388 }
389
390 /* Mark the MCB as the last MCB */
391 Mcb->BlockType = 'Z';
392
393 DosUmbLinked = FALSE;
394 return TRUE;
395 }
396
397 VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
398 {
399 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
400
401 /* Just set the owner */
402 Mcb->OwnerPsp = NewOwner;
403 }
404