2 * COPYRIGHT: GPLv2+ - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/himem.c
5 * PURPOSE: DOS XMS Driver and UMB Provider
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
9 * DOCUMENTATION: Official specifications:
10 * XMS v2.0: http://www.phatcode.net/res/219/files/xms20.txt
11 * XMS v3.0: http://www.phatcode.net/res/219/files/xms30.txt
13 * About the implementation of UMBs in DOS:
14 * ----------------------------------------
15 * DOS needs a UMB provider to be able to use chunks of RAM in the C000-EFF0
16 * memory region. A UMB provider detects regions of memory that do not contain
17 * any ROMs or other system mapped area such as video RAM.
19 * Where can UMB providers be found?
21 * The XMS specification (see himem.c) provides three APIs to create, free, and
22 * resize UMB blocks. As such, DOS performs calls into the XMS driver chain to
23 * request UMB blocks and include them into the DOS memory arena.
24 * However, is it only the HIMEM driver (implementing the XMS specification)
25 * which can provide UMBs? It appears that this is not necessarily the case:
26 * for example the MS HIMEM versions do not implement the UMB APIs; instead
27 * it is the EMS driver (EMM386) which provides them, by hooking into the XMS
28 * driver chain (see https://support.microsoft.com/en-us/kb/95555 : "MS-DOS 5.0
29 * and later EMM386.EXE can also be configured to provide UMBs according to the
30 * XMS. This causes EMM386.EXE to be a provider of the UMB portion of the XMS.").
32 * Some alternative replacements of HIMEM/EMM386 (for example in FreeDOS)
33 * implement everything inside only one driver (XMS+EMS+UMB provider).
34 * Finally there are some UMB providers that exist separately of XMS and EMS
35 * drivers (for example, UMBPCI): they use hardware-specific tricks to discover
38 * For more details, see:
39 * http://www.freedos.org/technotes/technote/txt/169.txt
40 * http://www.freedos.org/technotes/technote/txt/202.txt
41 * http://www.uncreativelabs.net/textfiles/system/UMB.TXT
43 * This DOS XMS Driver provides the UMB APIs that are implemented on top
44 * of the internal Upper Memory Area Manager, in umamgr.c
47 /* INCLUDES *******************************************************************/
56 #include "../../memory.h"
57 #include "bios/umamgr.h"
62 #define XMS_DEVICE_NAME "XMSXXXX0"
67 /* PRIVATE VARIABLES **********************************************************/
69 static const BYTE EntryProcedure
[] =
71 0xEB, // jmp short +0x03
82 static PDOS_DEVICE_NODE Node
= NULL
;
83 static XMS_HANDLE HandleTable
[XMS_MAX_HANDLES
];
84 static WORD FreeBlocks
= XMS_BLOCKS
;
85 static RTL_BITMAP AllocBitmap
;
86 static ULONG BitmapBuffer
[(XMS_BLOCKS
+ 31) / 32];
89 * This value is associated to HIMEM's "/HMAMIN=" switch. It indicates the
90 * minimum account of space in the HMA a program can use, and is used in
91 * conjunction with the "Request HMA" function.
93 * NOTE: The "/HMAMIN=" value is in kilo-bytes, whereas HmaMinSize is in bytes.
95 * Default value: 0. This causes the HMA to be allocated on a first come,
98 static WORD HmaMinSize
= 0;
100 * Flag used by "Request/Release HMA" functions, which indicates
101 * whether the HMA was reserved or not.
103 static BOOLEAN IsHmaReserved
= FALSE
;
106 * Flag used by "Global Enable/Disable A20" functions, so that they don't
107 * need to re-change the state of A20 if it was already enabled/disabled.
109 static BOOLEAN IsA20Enabled
= FALSE
;
111 * This flag is set to TRUE or FALSE when A20 line was already disabled or
112 * enabled when XMS driver was loaded.
113 * In case A20 was disabled, we are allowed to modify it. In case A20 was
114 * already enabled, we are not allowed to touch it.
116 static BOOLEAN CanChangeA20
= TRUE
;
118 * Count for enabling or disabling the A20 line. The A20 line is enabled
119 * only if the enabling count is greater than or equal to 0.
121 static LONG A20EnableCount
= 0;
123 /* A20 LINE HELPERS ***********************************************************/
125 static VOID
XmsLocalEnableA20(VOID
)
127 /* Enable A20 only if we can do so, otherwise make the caller believe we enabled it */
128 if (!CanChangeA20
) goto Quit
;
130 /* The count is zero so enable A20 */
131 if (A20EnableCount
== 0) EmulatorSetA20(TRUE
);
136 setAX(0x0001); /* Line successfully enabled */
137 setBL(XMS_STATUS_SUCCESS
);
141 static VOID
XmsLocalDisableA20(VOID
)
143 UCHAR Result
= XMS_STATUS_SUCCESS
;
145 /* Disable A20 only if we can do so, otherwise make the caller believe we disabled it */
146 if (!CanChangeA20
) goto Quit
;
148 /* If the count is already zero, fail */
149 if (A20EnableCount
== 0) goto Fail
;
153 /* The count is zero so disable A20 */
154 if (A20EnableCount
== 0)
155 EmulatorSetA20(FALSE
); // Result = XMS_STATUS_SUCCESS;
157 Result
= XMS_STATUS_A20_STILL_ENABLED
;
160 setAX(0x0001); /* Line successfully disabled */
165 setAX(0x0000); /* Line failed to be disabled */
166 setBL(XMS_STATUS_A20_ERROR
);
170 /* PRIVATE FUNCTIONS **********************************************************/
172 static inline PXMS_HANDLE
GetXmsHandleRecord(WORD Handle
)
175 if (Handle
== 0 || Handle
>= XMS_MAX_HANDLES
) return NULL
;
177 Entry
= &HandleTable
[Handle
- 1];
178 return Entry
->Size
? Entry
: NULL
;
181 static inline BOOLEAN
ValidateXmsHandle(PXMS_HANDLE HandleEntry
)
183 return (HandleEntry
!= NULL
&& HandleEntry
->Handle
!= 0);
186 static WORD
XmsGetLargestFreeBlock(VOID
)
189 DWORD CurrentIndex
= 0;
193 while (CurrentIndex
< XMS_BLOCKS
)
195 RunSize
= RtlFindNextForwardRunClear(&AllocBitmap
, CurrentIndex
, &RunStart
);
196 if (RunSize
== 0) break;
198 /* Update the maximum */
199 if (RunSize
> Result
) Result
= RunSize
;
201 /* Go to the next run */
202 CurrentIndex
= RunStart
+ RunSize
;
208 static UCHAR
XmsAlloc(WORD Size
, PWORD Handle
)
211 PXMS_HANDLE HandleEntry
;
212 DWORD CurrentIndex
= 0;
216 if (Size
> FreeBlocks
) return XMS_STATUS_OUT_OF_MEMORY
;
218 for (i
= 0; i
< XMS_MAX_HANDLES
; i
++)
220 HandleEntry
= &HandleTable
[i
];
221 if (HandleEntry
->Handle
== 0)
228 if (i
== XMS_MAX_HANDLES
) return XMS_STATUS_OUT_OF_HANDLES
;
230 /* Optimize blocks */
231 for (i
= 0; i
< XMS_MAX_HANDLES
; i
++)
233 /* Skip free and locked blocks */
234 if (HandleEntry
->Handle
== 0 || HandleEntry
->LockCount
> 0) continue;
236 CurrentIndex
= (HandleEntry
->Address
- XMS_ADDRESS
) / XMS_BLOCK_SIZE
;
238 /* Check if there is any free space before this block */
239 RunSize
= RtlFindLastBackwardRunClear(&AllocBitmap
, CurrentIndex
, &RunStart
);
240 if (RunSize
== 0) break;
242 /* Move this block back */
243 RtlMoveMemory((PVOID
)REAL_TO_PHYS(HandleEntry
->Address
- RunSize
* XMS_BLOCK_SIZE
),
244 (PVOID
)REAL_TO_PHYS(HandleEntry
->Address
),
245 RunSize
* XMS_BLOCK_SIZE
);
247 /* Update the address */
248 HandleEntry
->Address
-= RunSize
* XMS_BLOCK_SIZE
;
251 while (CurrentIndex
< XMS_BLOCKS
)
253 RunSize
= RtlFindNextForwardRunClear(&AllocBitmap
, CurrentIndex
, &RunStart
);
254 if (RunSize
== 0) break;
256 if (RunSize
>= HandleEntry
->Size
)
258 /* Allocate it here */
259 HandleEntry
->Handle
= i
+ 1;
260 HandleEntry
->LockCount
= 0;
261 HandleEntry
->Size
= Size
;
262 HandleEntry
->Address
= XMS_ADDRESS
+ RunStart
* XMS_BLOCK_SIZE
;
265 RtlSetBits(&AllocBitmap
, RunStart
, HandleEntry
->Size
);
267 return XMS_STATUS_SUCCESS
;
271 CurrentIndex
= RunStart
+ RunSize
;
274 return XMS_STATUS_OUT_OF_MEMORY
;
277 static UCHAR
XmsRealloc(WORD Handle
, WORD NewSize
)
280 PXMS_HANDLE HandleEntry
= GetXmsHandleRecord(Handle
);
281 DWORD CurrentIndex
= 0;
285 if (!ValidateXmsHandle(HandleEntry
))
286 return XMS_STATUS_INVALID_HANDLE
;
288 if (HandleEntry
->LockCount
)
289 return XMS_STATUS_LOCKED
;
291 /* Get the block number */
292 BlockNumber
= (HandleEntry
->Address
- XMS_ADDRESS
) / XMS_BLOCK_SIZE
;
294 if (NewSize
< HandleEntry
->Size
)
296 /* Just reduce the size of this block */
297 RtlClearBits(&AllocBitmap
, BlockNumber
+ NewSize
, HandleEntry
->Size
- NewSize
);
298 FreeBlocks
+= HandleEntry
->Size
- NewSize
;
299 HandleEntry
->Size
= NewSize
;
301 else if (NewSize
> HandleEntry
->Size
)
303 /* Check if we can expand in-place */
304 if (RtlAreBitsClear(&AllocBitmap
,
305 BlockNumber
+ HandleEntry
->Size
,
306 NewSize
- HandleEntry
->Size
))
308 /* Just increase the size of this block */
309 RtlSetBits(&AllocBitmap
,
310 BlockNumber
+ HandleEntry
->Size
,
311 NewSize
- HandleEntry
->Size
);
312 FreeBlocks
-= NewSize
- HandleEntry
->Size
;
313 HandleEntry
->Size
= NewSize
;
316 return XMS_STATUS_SUCCESS
;
319 /* Deallocate the current block range */
320 RtlClearBits(&AllocBitmap
, BlockNumber
, HandleEntry
->Size
);
322 /* Find a new place for this block */
323 while (CurrentIndex
< XMS_BLOCKS
)
325 RunSize
= RtlFindNextForwardRunClear(&AllocBitmap
, CurrentIndex
, &RunStart
);
326 if (RunSize
== 0) break;
328 if (RunSize
>= NewSize
)
330 /* Allocate the new range */
331 RtlSetBits(&AllocBitmap
, RunStart
, NewSize
);
333 /* Move the data to the new location */
334 RtlMoveMemory((PVOID
)REAL_TO_PHYS(XMS_ADDRESS
+ RunStart
* XMS_BLOCK_SIZE
),
335 (PVOID
)REAL_TO_PHYS(HandleEntry
->Address
),
336 HandleEntry
->Size
* XMS_BLOCK_SIZE
);
338 /* Update the handle entry */
339 HandleEntry
->Address
= XMS_ADDRESS
+ RunStart
* XMS_BLOCK_SIZE
;
340 HandleEntry
->Size
= NewSize
;
342 /* Update the free block counter */
343 FreeBlocks
-= NewSize
- HandleEntry
->Size
;
345 return XMS_STATUS_SUCCESS
;
349 CurrentIndex
= RunStart
+ RunSize
;
352 /* Restore the old block range */
353 RtlSetBits(&AllocBitmap
, BlockNumber
, HandleEntry
->Size
);
354 return XMS_STATUS_OUT_OF_MEMORY
;
357 return XMS_STATUS_SUCCESS
;
360 static UCHAR
XmsFree(WORD Handle
)
363 PXMS_HANDLE HandleEntry
= GetXmsHandleRecord(Handle
);
365 if (!ValidateXmsHandle(HandleEntry
))
366 return XMS_STATUS_INVALID_HANDLE
;
368 if (HandleEntry
->LockCount
)
369 return XMS_STATUS_LOCKED
;
371 BlockNumber
= (HandleEntry
->Address
- XMS_ADDRESS
) / XMS_BLOCK_SIZE
;
372 RtlClearBits(&AllocBitmap
, BlockNumber
, HandleEntry
->Size
);
374 HandleEntry
->Handle
= 0;
375 FreeBlocks
+= HandleEntry
->Size
;
377 return XMS_STATUS_SUCCESS
;
380 static UCHAR
XmsLock(WORD Handle
, PDWORD Address
)
382 PXMS_HANDLE HandleEntry
= GetXmsHandleRecord(Handle
);
384 if (!ValidateXmsHandle(HandleEntry
))
385 return XMS_STATUS_INVALID_HANDLE
;
387 if (HandleEntry
->LockCount
== 0xFF)
388 return XMS_STATUS_LOCK_OVERFLOW
;
390 /* Increment the lock count */
391 HandleEntry
->LockCount
++;
392 *Address
= HandleEntry
->Address
;
394 return XMS_STATUS_SUCCESS
;
397 static UCHAR
XmsUnlock(WORD Handle
)
399 PXMS_HANDLE HandleEntry
= GetXmsHandleRecord(Handle
);
401 if (!ValidateXmsHandle(HandleEntry
))
402 return XMS_STATUS_INVALID_HANDLE
;
404 if (!HandleEntry
->LockCount
)
405 return XMS_STATUS_NOT_LOCKED
;
407 /* Decrement the lock count */
408 HandleEntry
->LockCount
--;
410 return XMS_STATUS_SUCCESS
;
413 static VOID WINAPI
XmsBopProcedure(LPWORD Stack
)
417 /* Get XMS Version */
420 setAX(0x0300); /* XMS version 3.00 */
421 setBX(0x0301); /* Driver version 3.01 */
422 setDX(0x0001); /* HMA present */
429 /* Check whether HMA is already reserved */
432 /* It is, bail out */
434 setBL(XMS_STATUS_HMA_IN_USE
);
438 // NOTE: We implicitely suppose that we always have HMA.
439 // If not, we should fail there with the XMS_STATUS_HMA_DOES_NOT_EXIST
442 /* Check whether the requested size is above the minimal allowed one */
443 if (getDX() < HmaMinSize
)
445 /* It is not, bail out */
447 setBL(XMS_STATUS_HMA_MIN_SIZE
);
452 IsHmaReserved
= TRUE
;
454 setBL(XMS_STATUS_SUCCESS
);
461 /* Check whether HMA was reserved */
464 /* It was not, bail out */
466 setBL(XMS_STATUS_HMA_NOT_ALLOCATED
);
471 IsHmaReserved
= FALSE
;
473 setBL(XMS_STATUS_SUCCESS
);
477 /* Global Enable A20 */
480 /* Enable A20 if needed */
484 if (getAX() != 0x0001)
486 /* XmsLocalEnableA20 failed and already set AX and BL to their correct values */
493 setAX(0x0001); /* Line successfully enabled */
494 setBL(XMS_STATUS_SUCCESS
);
498 /* Global Disable A20 */
501 UCHAR Result
= XMS_STATUS_SUCCESS
;
503 /* Disable A20 if needed */
506 XmsLocalDisableA20();
507 if (getAX() != 0x0001)
509 /* XmsLocalDisableA20 failed and already set AX and BL to their correct values */
513 IsA20Enabled
= FALSE
;
517 setAX(0x0001); /* Line successfully disabled */
522 /* Local Enable A20 */
525 /* This call sets AX and BL to their correct values */
530 /* Local Disable A20 */
533 /* This call sets AX and BL to their correct values */
534 XmsLocalDisableA20();
538 /* Query A20 State */
541 setAX(EmulatorGetA20());
542 setBL(XMS_STATUS_SUCCESS
);
546 /* Query Free Extended Memory */
549 setAX(XmsGetLargestFreeBlock());
553 setBL(XMS_STATUS_SUCCESS
);
555 setBL(XMS_STATUS_OUT_OF_MEMORY
);
560 /* Allocate Extended Memory Block */
564 UCHAR Result
= XmsAlloc(getDX(), &Handle
);
566 if (Result
== XMS_STATUS_SUCCESS
)
580 /* Free Extended Memory Block */
583 UCHAR Result
= XmsFree(getDX());
585 setAX(Result
== XMS_STATUS_SUCCESS
);
590 /* Move Extended Memory Block */
593 PVOID SourceAddress
, DestAddress
;
594 PXMS_COPY_DATA CopyData
= (PXMS_COPY_DATA
)SEG_OFF_TO_PTR(getDS(), getSI());
595 PXMS_HANDLE HandleEntry
;
597 if (CopyData
->SourceHandle
)
599 HandleEntry
= GetXmsHandleRecord(CopyData
->SourceHandle
);
600 if (!ValidateXmsHandle(HandleEntry
))
603 setBL(XMS_STATUS_BAD_SRC_HANDLE
);
607 if (CopyData
->SourceOffset
>= HandleEntry
->Size
* XMS_BLOCK_SIZE
)
610 setBL(XMS_STATUS_BAD_SRC_OFFSET
);
613 SourceAddress
= (PVOID
)REAL_TO_PHYS(HandleEntry
->Address
+ CopyData
->SourceOffset
);
617 /* The offset is actually a 16-bit segment:offset pointer */
618 SourceAddress
= FAR_POINTER(CopyData
->SourceOffset
);
621 if (CopyData
->DestHandle
)
623 HandleEntry
= GetXmsHandleRecord(CopyData
->DestHandle
);
624 if (!ValidateXmsHandle(HandleEntry
))
627 setBL(XMS_STATUS_BAD_DEST_HANDLE
);
631 if (CopyData
->DestOffset
>= HandleEntry
->Size
* XMS_BLOCK_SIZE
)
634 setBL(XMS_STATUS_BAD_DEST_OFFSET
);
637 DestAddress
= (PVOID
)REAL_TO_PHYS(HandleEntry
->Address
+ CopyData
->DestOffset
);
641 /* The offset is actually a 16-bit segment:offset pointer */
642 DestAddress
= FAR_POINTER(CopyData
->DestOffset
);
645 /* Perform the move */
646 RtlMoveMemory(DestAddress
, SourceAddress
, CopyData
->Count
);
649 setBL(XMS_STATUS_SUCCESS
);
653 /* Lock Extended Memory Block */
657 UCHAR Result
= XmsLock(getDX(), &Address
);
659 if (Result
== XMS_STATUS_SUCCESS
)
663 /* Store the LINEAR address in DX:BX */
664 setDX(HIWORD(Address
));
665 setBX(LOWORD(Address
));
676 /* Unlock Extended Memory Block */
679 UCHAR Result
= XmsUnlock(getDX());
681 setAX(Result
== XMS_STATUS_SUCCESS
);
686 /* Get Handle Information */
689 PXMS_HANDLE HandleEntry
= GetXmsHandleRecord(getDX());
693 if (!ValidateXmsHandle(HandleEntry
))
696 setBL(XMS_STATUS_INVALID_HANDLE
);
700 for (i
= 0; i
< XMS_MAX_HANDLES
; i
++)
702 if (HandleTable
[i
].Handle
== 0) Handles
++;
706 setBH(HandleEntry
->LockCount
);
708 setDX(HandleEntry
->Size
);
712 /* Reallocate Extended Memory Block */
715 UCHAR Result
= XmsRealloc(getDX(), getBX());
717 setAX(Result
== XMS_STATUS_SUCCESS
);
726 USHORT Segment
= 0x0000; /* No preferred segment */
727 USHORT Size
= getDX(); /* Size is in paragraphs */
729 Result
= UmaDescReserve(&Segment
, &Size
);
733 setBL(Size
> 0 ? XMS_STATUS_SMALLER_UMB
: XMS_STATUS_OUT_OF_UMBS
);
744 USHORT Segment
= getDX();
746 Result
= UmaDescRelease(Segment
);
748 setBL(XMS_STATUS_INVALID_UMB
);
758 USHORT Segment
= getDX();
759 USHORT Size
= getBX(); /* Size is in paragraphs */
761 Result
= UmaDescReallocate(Segment
, &Size
);
766 setBL(XMS_STATUS_SMALLER_UMB
);
771 setBL(XMS_STATUS_INVALID_UMB
);
781 DPRINT1("XMS command AH = 0x%02X NOT IMPLEMENTED\n", getAH());
782 setBL(XMS_STATUS_NOT_IMPLEMENTED
);
787 /* PUBLIC FUNCTIONS ***********************************************************/
789 BOOLEAN
XmsGetDriverEntry(PDWORD Pointer
)
791 if (Node
== NULL
) return FALSE
;
792 *Pointer
= DEVICE_PRIVATE_AREA(Node
->Driver
);
796 VOID
XmsInitialize(VOID
)
798 RtlZeroMemory(HandleTable
, sizeof(HandleTable
));
799 RtlZeroMemory(BitmapBuffer
, sizeof(BitmapBuffer
));
800 RtlInitializeBitMap(&AllocBitmap
, BitmapBuffer
, XMS_BLOCKS
);
802 Node
= DosCreateDeviceEx(DOS_DEVATTR_IOCTL
| DOS_DEVATTR_CHARACTER
,
804 sizeof(EntryProcedure
));
806 RegisterBop(BOP_XMS
, XmsBopProcedure
);
808 /* Copy the entry routine to the device private area */
809 RtlMoveMemory(FAR_POINTER(DEVICE_PRIVATE_AREA(Node
->Driver
)),
811 sizeof(EntryProcedure
));
814 VOID
XmsCleanup(VOID
)
816 RegisterBop(BOP_XMS
, NULL
);
817 DosDeleteDevice(Node
);