3952194622f49f924a653f2b973d653abeb33175
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dos32krnl / himem.c
1 /*
2 * COPYRIGHT: GPLv2+ - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: himem.c
5 * PURPOSE: DOS XMS Driver
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 "cpu/bop.h"
16 #include "../../memory.h"
17 #include "io.h"
18 #include "hardware/ps2.h"
19
20 #include "dos.h"
21 #include "dos/dem.h"
22 #include "device.h"
23 #include "himem.h"
24 #include "memory.h"
25
26 #define XMS_DEVICE_NAME "XMSXXXX0"
27
28 /* BOP Identifiers */
29 #define BOP_XMS 0x52
30
31 ULONG
32 NTAPI
33 RtlFindLastBackwardRunClear
34 (
35 IN PRTL_BITMAP BitMapHeader,
36 IN ULONG FromIndex,
37 OUT PULONG StartingRunIndex
38 );
39
40 /* PRIVATE VARIABLES **********************************************************/
41
42 static const BYTE EntryProcedure[] = {
43 0xEB, // jmp short +0x03
44 0x03,
45 0x90, // nop
46 0x90, // nop
47 0x90, // nop
48 LOBYTE(EMULATOR_BOP),
49 HIBYTE(EMULATOR_BOP),
50 BOP_XMS,
51 0xCB // retf
52 };
53
54 static PDOS_DEVICE_NODE Node = NULL;
55 static XMS_HANDLE HandleTable[XMS_MAX_HANDLES];
56 static WORD FreeBlocks = XMS_BLOCKS;
57 static RTL_BITMAP AllocBitmap;
58 static ULONG BitmapBuffer[(XMS_BLOCKS + 31) / 32];
59
60 /*
61 * Flag used by Global Enable/Disable A20 functions, so that they don't
62 * need to re-change the state of A20 if it was already enabled/disabled.
63 */
64 static BOOLEAN IsA20Enabled = FALSE;
65 /*
66 * This flag is set to TRUE or FALSE when A20 line was already disabled or
67 * enabled when XMS driver was loaded.
68 * In case A20 was disabled, we are allowed to modify it. In case A20 was
69 * already enabled, we are not allowed to touch it.
70 */
71 static BOOLEAN CanChangeA20 = TRUE;
72 /*
73 * Count for enabling or disabling the A20 line. The A20 line is enabled
74 * only if the enabling count is greater than or equal to 0.
75 */
76 static LONG A20EnableCount = 0;
77
78 /* HELPERS FOR A20 LINE *******************************************************/
79
80 static VOID XmsLocalEnableA20(VOID)
81 {
82 /* Enable A20 only if we can do so, otherwise make the caller believe we enabled it */
83 if (!CanChangeA20) goto Quit;
84
85 /* The count is zero so enable A20 */
86 if (A20EnableCount == 0) EmulatorSetA20(TRUE);
87
88 ++A20EnableCount;
89
90 Quit:
91 setAX(0x0001); /* Line successfully enabled */
92 setBL(XMS_STATUS_SUCCESS);
93 return;
94 }
95
96 static VOID XmsLocalDisableA20(VOID)
97 {
98 /* Disable A20 only if we can do so, otherwise make the caller believe we disabled it */
99 if (!CanChangeA20) goto Quit;
100
101 /* If the count is already zero, fail */
102 if (A20EnableCount == 0) goto Fail;
103
104 --A20EnableCount;
105
106 /* The count is zero so disable A20 */
107 if (A20EnableCount == 0) EmulatorSetA20(FALSE);
108
109 Quit:
110 setAX(0x0001); /* Line successfully disabled */
111 setBL(XMS_STATUS_SUCCESS);
112 return;
113
114 Fail:
115 setAX(0x0000); /* Line failed to be enabled */
116 setBL(XMS_STATUS_A20_ERROR);
117 return;
118 }
119
120 /* PRIVATE FUNCTIONS **********************************************************/
121
122 static inline PXMS_HANDLE GetHandleRecord(WORD Handle)
123 {
124 PXMS_HANDLE Entry;
125 if (Handle == 0 || Handle >= XMS_MAX_HANDLES) return NULL;
126
127 Entry = &HandleTable[Handle - 1];
128 return Entry->Size ? Entry : NULL;
129 }
130
131 static WORD XmsGetLargestFreeBlock(VOID)
132 {
133 WORD Result = 0;
134 DWORD CurrentIndex = 0;
135 ULONG RunStart;
136 ULONG RunSize;
137
138 while (CurrentIndex < XMS_BLOCKS)
139 {
140 RunSize = RtlFindNextForwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
141 if (RunSize == 0) break;
142
143 /* Update the maximum */
144 if (RunSize > Result) Result = RunSize;
145
146 /* Go to the next run */
147 CurrentIndex = RunStart + RunSize;
148 }
149
150 return Result;
151 }
152
153 static CHAR XmsAlloc(WORD Size, PWORD Handle)
154 {
155 BYTE i;
156 PXMS_HANDLE HandleEntry;
157 DWORD CurrentIndex = 0;
158 ULONG RunStart;
159 ULONG RunSize;
160
161 if (Size > FreeBlocks) return XMS_STATUS_OUT_OF_MEMORY;
162
163 for (i = 0; i < XMS_MAX_HANDLES; i++)
164 {
165 HandleEntry = &HandleTable[i];
166 if (HandleEntry->Handle == 0)
167 {
168 *Handle = i + 1;
169 break;
170 }
171 }
172
173 if (i == XMS_MAX_HANDLES) return XMS_STATUS_OUT_OF_HANDLES;
174
175 /* Optimize blocks */
176 for (i = 0; i < XMS_MAX_HANDLES; i++)
177 {
178 /* Skip free and locked blocks */
179 if (HandleEntry->Handle == 0 || HandleEntry->LockCount > 0) continue;
180
181 CurrentIndex = (HandleEntry->Address - XMS_ADDRESS) / XMS_BLOCK_SIZE;
182
183 /* Check if there is any free space before this block */
184 RunSize = RtlFindLastBackwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
185 if (RunSize == 0) break;
186
187 /* Move this block back */
188 RtlMoveMemory((PVOID)REAL_TO_PHYS(HandleEntry->Address - RunSize * XMS_BLOCK_SIZE),
189 (PVOID)REAL_TO_PHYS(HandleEntry->Address),
190 RunSize * XMS_BLOCK_SIZE);
191
192 /* Update the address */
193 HandleEntry->Address -= RunSize * XMS_BLOCK_SIZE;
194 }
195
196 while (CurrentIndex < XMS_BLOCKS)
197 {
198 RunSize = RtlFindNextForwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
199 if (RunSize == 0) break;
200
201 if (RunSize >= HandleEntry->Size)
202 {
203 /* Allocate it here */
204 HandleEntry->Handle = i + 1;
205 HandleEntry->LockCount = 0;
206 HandleEntry->Size = Size;
207 HandleEntry->Address = XMS_ADDRESS + RunStart * XMS_BLOCK_SIZE;
208
209 FreeBlocks -= Size;
210 RtlSetBits(&AllocBitmap, RunStart, HandleEntry->Size);
211
212 return XMS_STATUS_SUCCESS;
213 }
214
215 /* Keep searching */
216 CurrentIndex = RunStart + RunSize;
217 }
218
219 return XMS_STATUS_OUT_OF_MEMORY;
220 }
221
222 static CHAR XmsFree(WORD Handle)
223 {
224 DWORD BlockNumber;
225 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
226
227 if (HandleEntry == NULL || HandleEntry->Handle == 0) return XMS_STATUS_INVALID_HANDLE;
228 if (HandleEntry->LockCount) return XMS_STATUS_LOCKED;
229
230 BlockNumber = (HandleEntry->Address - XMS_ADDRESS) / XMS_BLOCK_SIZE;
231 RtlClearBits(&AllocBitmap, BlockNumber, HandleEntry->Size);
232
233 HandleEntry->Handle = 0;
234 FreeBlocks += HandleEntry->Size;
235
236 return XMS_STATUS_SUCCESS;
237 }
238
239 static CHAR XmsLock(WORD Handle, PDWORD Address)
240 {
241 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
242
243 if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
244 if (HandleEntry->LockCount == 0xFF) return XMS_STATUS_LOCK_OVERFLOW;
245
246 /* Increment the lock count */
247 HandleEntry->LockCount++;
248 *Address = HandleEntry->Address;
249
250 return XMS_STATUS_SUCCESS;
251 }
252
253 static CHAR XmsUnlock(WORD Handle)
254 {
255 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
256
257 if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
258 if (!HandleEntry->LockCount) return XMS_STATUS_NOT_LOCKED;
259
260 /* Decrement the lock count */
261 HandleEntry->LockCount--;
262
263 return XMS_STATUS_SUCCESS;
264 }
265
266 static VOID WINAPI XmsBopProcedure(LPWORD Stack)
267 {
268 switch (getAH())
269 {
270 /* Get XMS Version */
271 case 0x00:
272 {
273 setAX(0x0300); /* XMS version 3.00 */
274 setBX(0x0301); /* Driver version 3.01 */
275 setDX(0x0001); /* HMA present */
276 break;
277 }
278
279 /* Global Enable A20 */
280 case 0x03:
281 {
282 /* Enable A20 if needed */
283 if (!IsA20Enabled)
284 {
285 XmsLocalEnableA20();
286 if (getAX() != 1)
287 {
288 /* XmsLocalEnableA20 failed and already set AX and BL to their correct values */
289 break;
290 }
291
292 IsA20Enabled = TRUE;
293 }
294
295 setAX(0x0001); /* Line successfully enabled */
296 setBL(XMS_STATUS_SUCCESS);
297 break;
298 }
299
300 /* Global Disable A20 */
301 case 0x04:
302 {
303 /* Disable A20 if needed */
304 if (IsA20Enabled)
305 {
306 XmsLocalDisableA20();
307 if (getAX() != 1)
308 {
309 /* XmsLocalDisableA20 failed and already set AX and BL to their correct values */
310 break;
311 }
312
313 IsA20Enabled = FALSE;
314 }
315
316 setAX(0x0001); /* Line successfully disabled */
317 setBL(XMS_STATUS_SUCCESS);
318 break;
319 }
320
321 /* Local Enable A20 */
322 case 0x05:
323 {
324 /* This call sets AX and BL to their correct values */
325 XmsLocalEnableA20();
326 break;
327 }
328
329 /* Local Disable A20 */
330 case 0x06:
331 {
332 /* This call sets AX and BL to their correct values */
333 XmsLocalDisableA20();
334 break;
335 }
336
337 /* Query A20 State */
338 case 0x07:
339 {
340 setAX(EmulatorGetA20());
341 setBL(XMS_STATUS_SUCCESS);
342 break;
343 }
344
345 /* Query Free Extended Memory */
346 case 0x08:
347 {
348 setAX(XmsGetLargestFreeBlock());
349 setDX(FreeBlocks);
350 setBL(XMS_STATUS_SUCCESS);
351 break;
352 }
353
354 /* Allocate Extended Memory Block */
355 case 0x09:
356 {
357 WORD Handle;
358 CHAR Result = XmsAlloc(getDX(), &Handle);
359
360 if (Result >= 0)
361 {
362 setAX(1);
363 setDX(Handle);
364 }
365 else
366 {
367 setAX(0);
368 setBL(Result);
369 }
370
371 break;
372 }
373
374 /* Free Extended Memory Block */
375 case 0x0A:
376 {
377 CHAR Result = XmsFree(getDX());
378
379 setAX(Result >= 0);
380 setBL(Result);
381
382 break;
383 }
384
385 /* Move Extended Memory Block */
386 case 0x0B:
387 {
388 PVOID SourceAddress, DestAddress;
389 PXMS_COPY_DATA CopyData = (PXMS_COPY_DATA)SEG_OFF_TO_PTR(getDS(), getSI());
390
391 if (CopyData->SourceHandle)
392 {
393 PXMS_HANDLE Entry = GetHandleRecord(CopyData->SourceHandle);
394 if (!Entry)
395 {
396 setAX(0);
397 setBL(XMS_STATUS_BAD_SRC_HANDLE);
398 break;
399 }
400
401 if (CopyData->SourceOffset >= Entry->Size * XMS_BLOCK_SIZE)
402 {
403 setAX(0);
404 setBL(XMS_STATUS_BAD_SRC_OFFSET);
405 }
406
407 SourceAddress = (PVOID)REAL_TO_PHYS(Entry->Address + CopyData->SourceOffset);
408 }
409 else
410 {
411 /* The offset is actually a 16-bit segment:offset pointer */
412 SourceAddress = SEG_OFF_TO_PTR(HIWORD(CopyData->SourceOffset),
413 LOWORD(CopyData->SourceOffset));
414 }
415
416 if (CopyData->DestHandle)
417 {
418 PXMS_HANDLE Entry = GetHandleRecord(CopyData->DestHandle);
419 if (!Entry)
420 {
421 setAX(0);
422 setBL(XMS_STATUS_BAD_DEST_HANDLE);
423 break;
424 }
425
426 if (CopyData->DestOffset >= Entry->Size * XMS_BLOCK_SIZE)
427 {
428 setAX(0);
429 setBL(XMS_STATUS_BAD_DEST_OFFSET);
430 }
431
432 DestAddress = (PVOID)REAL_TO_PHYS(Entry->Address + CopyData->DestOffset);
433 }
434 else
435 {
436 /* The offset is actually a 16-bit segment:offset pointer */
437 DestAddress = SEG_OFF_TO_PTR(HIWORD(CopyData->DestOffset),
438 LOWORD(CopyData->DestOffset));
439 }
440
441 setAX(1);
442 setBL(XMS_STATUS_SUCCESS);
443 RtlMoveMemory(DestAddress, SourceAddress, CopyData->Count);
444 break;
445 }
446
447 /* Lock Extended Memory Block */
448 case 0x0C:
449 {
450 DWORD Address;
451 CHAR Result = XmsLock(getDX(), &Address);
452
453 if (Result >= 0)
454 {
455 setAX(1);
456
457 /* Store the LINEAR address in DX:BX */
458 setDX(HIWORD(Address));
459 setBX(LOWORD(Address));
460 }
461 else
462 {
463 setAX(0);
464 setBL(Result);
465 }
466
467 break;
468 }
469
470 /* Unlock Extended Memory Block */
471 case 0x0D:
472 {
473 CHAR Result = XmsUnlock(getDX());
474
475 setAX(Result >= 0);
476 setBL(Result);
477
478 break;
479 }
480
481 /* Get Handle Information */
482 case 0x0E:
483 {
484 PXMS_HANDLE Entry = GetHandleRecord(getDX());
485
486 if (Entry && Entry->Handle != 0)
487 {
488 UINT i;
489 UCHAR Handles = 0;
490
491 for (i = 0; i < XMS_MAX_HANDLES; i++)
492 {
493 if (HandleTable[i].Handle == 0) Handles++;
494 }
495
496 setAX(1);
497 setBH(Entry->LockCount);
498 setBL(Handles);
499 setDX(Entry->Size);
500 }
501 else
502 {
503 setAX(0);
504 setBL(XMS_STATUS_INVALID_HANDLE);
505 }
506
507 break;
508 }
509
510 /* Allocate UMB */
511 case 0x10:
512 {
513 WORD Segment;
514 WORD MaxAvailable;
515 BYTE OldAllocStrategy = Sda->AllocStrategy;
516 BOOLEAN OldLinkState = DosUmbLinked;
517
518 DosLinkUmb();
519 Sda->AllocStrategy = DOS_ALLOC_HIGH | DOS_ALLOC_BEST_FIT;
520 Segment = DosAllocateMemory(getDX(), &MaxAvailable);
521
522 if (Segment)
523 {
524 setAX(1);
525 setBX(Segment);
526 }
527 else
528 {
529 setAX(0);
530 setBL(MaxAvailable ? XMS_STATUS_SMALLER_UMB : XMS_STATUS_OUT_OF_UMBS);
531 setDX(MaxAvailable);
532 }
533
534 Sda->AllocStrategy = OldAllocStrategy;
535 if (!OldLinkState) DosUnlinkUmb();
536 break;
537 }
538
539 /* Free UMB */
540 case 0x11:
541 {
542 if (DosFreeMemory(getDX()))
543 {
544 setAX(1);
545 }
546 else
547 {
548 setAX(0);
549 setBL(XMS_STATUS_INVALID_UMB);
550 }
551
552 break;
553 }
554
555 /* Reallocate UMB */
556 case 0x12:
557 {
558 WORD Segment;
559 WORD MaxAvailable;
560
561 Segment = DosResizeMemory(getDX(), getBX(), &MaxAvailable);
562
563 if (Segment)
564 {
565 setAX(1);
566 }
567 else
568 {
569 setAX(0);
570 setBL(Sda->LastErrorCode == ERROR_INVALID_HANDLE
571 ? XMS_STATUS_INVALID_UMB : XMS_STATUS_SMALLER_UMB);
572 setDX(MaxAvailable);
573 }
574
575 break;
576 }
577
578 default:
579 {
580 DPRINT1("XMS command AH = 0x%02X NOT IMPLEMENTED\n", getAH());
581 setBL(XMS_STATUS_NOT_IMPLEMENTED);
582 }
583 }
584 }
585
586 /* PUBLIC FUNCTIONS ***********************************************************/
587
588 BOOLEAN XmsGetDriverEntry(PDWORD Pointer)
589 {
590 if (Node == NULL) return FALSE;
591 *Pointer = DEVICE_PRIVATE_AREA(Node->Driver);
592 return TRUE;
593 }
594
595 VOID XmsInitialize(VOID)
596 {
597 RtlZeroMemory(HandleTable, sizeof(HandleTable));
598 RtlZeroMemory(BitmapBuffer, sizeof(BitmapBuffer));
599 RtlInitializeBitMap(&AllocBitmap, BitmapBuffer, XMS_BLOCKS);
600
601 Node = DosCreateDeviceEx(DOS_DEVATTR_IOCTL | DOS_DEVATTR_CHARACTER,
602 XMS_DEVICE_NAME,
603 sizeof(EntryProcedure));
604
605 RegisterBop(BOP_XMS, XmsBopProcedure);
606
607 /* Copy the entry routine to the device private area */
608 RtlMoveMemory(FAR_POINTER(DEVICE_PRIVATE_AREA(Node->Driver)),
609 EntryProcedure,
610 sizeof(EntryProcedure));
611 }
612
613 VOID XmsCleanup(VOID)
614 {
615 RegisterBop(BOP_XMS, NULL);
616 DosDeleteDevice(Node);
617 }