* COPYRIGHT: GPLv2+ - See COPYING in the top level directory
* PROJECT: ReactOS Virtual DOS Machine
* FILE: himem.c
- * PURPOSE: DOS XMS Driver
+ * PURPOSE: DOS XMS Driver and UMB Provider
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
+ *
+ * DOCUMENTATION: Official specifications:
+ * XMS v2.0: http://www.phatcode.net/res/219/files/xms20.txt
+ * XMS v3.0: http://www.phatcode.net/res/219/files/xms30.txt
+ *
+ * About the implementation of UMBs in DOS:
+ * ----------------------------------------
+ * DOS needs a UMB provider to be able to use chunks of RAM in the C000-EFF0
+ * memory region. A UMB provider detects regions of memory that do not contain
+ * any ROMs or other system mapped area such as video RAM.
+ *
+ * Where can UMB providers be found?
+ *
+ * The XMS specification (see himem.c) provides three APIs to create, free, and
+ * resize UMB blocks. As such, DOS performs calls into the XMS driver chain to
+ * request UMB blocks and include them into the DOS memory arena.
+ * However, is it only the HIMEM driver (implementing the XMS specification)
+ * which can provide UMBs? It appears that this is not necessarily the case:
+ * for example the MS HIMEM versions do not implement the UMB APIs; instead
+ * it is the EMS driver (EMM386) which provides them, by hooking into the XMS
+ * driver chain (see https://support.microsoft.com/en-us/kb/95555 : "MS-DOS 5.0
+ * and later EMM386.EXE can also be configured to provide UMBs according to the
+ * XMS. This causes EMM386.EXE to be a provider of the UMB portion of the XMS.").
+ *
+ * Some alternative replacements of HIMEM/EMM386 (for example in FreeDOS)
+ * implement everything inside only one driver (XMS+EMS+UMB provider).
+ * Finally there are some UMB providers that exist separately of XMS and EMS
+ * drivers (for example, UMBPCI): they use hardware-specific tricks to discover
+ * and provide UMBs.
+ *
+ * For more details, see:
+ * http://www.freedos.org/technotes/technote/txt/169.txt
+ * http://www.freedos.org/technotes/technote/txt/202.txt
+ * http://www.uncreativelabs.net/textfiles/system/UMB.TXT
+ *
+ * This DOS XMS Driver provides the UMB APIs that are implemented on top
+ * of the internal Upper Memory Area Manager, in umamgr.c
*/
/* INCLUDES *******************************************************************/
#include "emulator.h"
#include "cpu/bop.h"
#include "../../memory.h"
-#include "io.h"
-#include "hardware/ps2.h"
+#include "bios/umamgr.h"
-#include "dos.h"
-#include "dos/dem.h"
#include "device.h"
#include "himem.h"
-#include "memory.h"
#define XMS_DEVICE_NAME "XMSXXXX0"
/* PRIVATE VARIABLES **********************************************************/
-static const BYTE EntryProcedure[] = {
+static const BYTE EntryProcedure[] =
+{
0xEB, // jmp short +0x03
0x03,
0x90, // nop
static ULONG BitmapBuffer[(XMS_BLOCKS + 31) / 32];
/*
- * Flag used by Global Enable/Disable A20 functions, so that they don't
+ * This value is associated to HIMEM's "/HMAMIN=" switch. It indicates the
+ * minimum account of space in the HMA a program can use, and is used in
+ * conjunction with the "Request HMA" function.
+ *
+ * NOTE: The "/HMAMIN=" value is in kilo-bytes, whereas HmaMinSize is in bytes.
+ *
+ * Default value: 0. This causes the HMA to be allocated on a first come,
+ * first served basis.
+ */
+static WORD HmaMinSize = 0;
+/*
+ * Flag used by "Request/Release HMA" functions, which indicates
+ * whether the HMA was reserved or not.
+ */
+static BOOLEAN IsHmaReserved = FALSE;
+
+/*
+ * Flag used by "Global Enable/Disable A20" functions, so that they don't
* need to re-change the state of A20 if it was already enabled/disabled.
*/
static BOOLEAN IsA20Enabled = FALSE;
*/
static LONG A20EnableCount = 0;
-/* HELPERS FOR A20 LINE *******************************************************/
+/* A20 LINE HELPERS ***********************************************************/
static VOID XmsLocalEnableA20(VOID)
{
static VOID XmsLocalDisableA20(VOID)
{
+ UCHAR Result = XMS_STATUS_SUCCESS;
+
/* Disable A20 only if we can do so, otherwise make the caller believe we disabled it */
if (!CanChangeA20) goto Quit;
--A20EnableCount;
/* The count is zero so disable A20 */
- if (A20EnableCount == 0) EmulatorSetA20(FALSE);
+ if (A20EnableCount == 0)
+ EmulatorSetA20(FALSE); // Result = XMS_STATUS_SUCCESS;
+ else
+ Result = XMS_STATUS_A20_STILL_ENABLED;
Quit:
setAX(0x0001); /* Line successfully disabled */
- setBL(XMS_STATUS_SUCCESS);
+ setBL(Result);
return;
Fail:
- setAX(0x0000); /* Line failed to be enabled */
+ setAX(0x0000); /* Line failed to be disabled */
setBL(XMS_STATUS_A20_ERROR);
return;
}
return Entry->Size ? Entry : NULL;
}
+static inline BOOLEAN ValidateHandle(PXMS_HANDLE HandleEntry)
+{
+ return (HandleEntry != NULL && HandleEntry->Handle != 0);
+}
+
static WORD XmsGetLargestFreeBlock(VOID)
{
WORD Result = 0;
return Result;
}
-static CHAR XmsAlloc(WORD Size, PWORD Handle)
+static UCHAR XmsAlloc(WORD Size, PWORD Handle)
{
BYTE i;
PXMS_HANDLE HandleEntry;
return XMS_STATUS_OUT_OF_MEMORY;
}
-static CHAR XmsFree(WORD Handle)
+static UCHAR XmsFree(WORD Handle)
{
DWORD BlockNumber;
PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
- if (HandleEntry == NULL || HandleEntry->Handle == 0) return XMS_STATUS_INVALID_HANDLE;
- if (HandleEntry->LockCount) return XMS_STATUS_LOCKED;
+ if (!ValidateHandle(HandleEntry))
+ return XMS_STATUS_INVALID_HANDLE;
+
+ if (HandleEntry->LockCount)
+ return XMS_STATUS_LOCKED;
BlockNumber = (HandleEntry->Address - XMS_ADDRESS) / XMS_BLOCK_SIZE;
RtlClearBits(&AllocBitmap, BlockNumber, HandleEntry->Size);
return XMS_STATUS_SUCCESS;
}
-static CHAR XmsLock(WORD Handle, PDWORD Address)
+static UCHAR XmsLock(WORD Handle, PDWORD Address)
{
PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
- if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
- if (HandleEntry->LockCount == 0xFF) return XMS_STATUS_LOCK_OVERFLOW;
+ if (!ValidateHandle(HandleEntry))
+ return XMS_STATUS_INVALID_HANDLE;
+
+ if (HandleEntry->LockCount == 0xFF)
+ return XMS_STATUS_LOCK_OVERFLOW;
/* Increment the lock count */
HandleEntry->LockCount++;
return XMS_STATUS_SUCCESS;
}
-static CHAR XmsUnlock(WORD Handle)
+static UCHAR XmsUnlock(WORD Handle)
{
PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
- if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
- if (!HandleEntry->LockCount) return XMS_STATUS_NOT_LOCKED;
+ if (!ValidateHandle(HandleEntry))
+ return XMS_STATUS_INVALID_HANDLE;
+
+ if (!HandleEntry->LockCount)
+ return XMS_STATUS_NOT_LOCKED;
/* Decrement the lock count */
HandleEntry->LockCount--;
break;
}
+ /* Request HMA */
+ case 0x01:
+ {
+ /* Check whether HMA is already reserved */
+ if (IsHmaReserved)
+ {
+ /* It is, bail out */
+ setAX(0x0000);
+ setBL(XMS_STATUS_HMA_IN_USE);
+ break;
+ }
+
+ // NOTE: We implicitely suppose that we always have HMA.
+ // If not, we should fail there with the XMS_STATUS_HMA_DOES_NOT_EXIST
+ // error code.
+
+ /* Check whether the requested size is above the minimal allowed one */
+ if (getDX() < HmaMinSize)
+ {
+ /* It is not, bail out */
+ setAX(0x0000);
+ setBL(XMS_STATUS_HMA_MIN_SIZE);
+ break;
+ }
+
+ /* Reserve it */
+ IsHmaReserved = TRUE;
+ setAX(0x0001);
+ setBL(XMS_STATUS_SUCCESS);
+ break;
+ }
+
+ /* Release HMA */
+ case 0x02:
+ {
+ /* Check whether HMA was reserved */
+ if (!IsHmaReserved)
+ {
+ /* It was not, bail out */
+ setAX(0x0000);
+ setBL(XMS_STATUS_HMA_NOT_ALLOCATED);
+ break;
+ }
+
+ /* Release it */
+ IsHmaReserved = FALSE;
+ setAX(0x0001);
+ setBL(XMS_STATUS_SUCCESS);
+ break;
+ }
+
/* Global Enable A20 */
case 0x03:
{
if (!IsA20Enabled)
{
XmsLocalEnableA20();
- if (getAX() != 1)
+ if (getAX() != 0x0001)
{
/* XmsLocalEnableA20 failed and already set AX and BL to their correct values */
break;
/* Global Disable A20 */
case 0x04:
{
+ UCHAR Result = XMS_STATUS_SUCCESS;
+
/* Disable A20 if needed */
if (IsA20Enabled)
{
XmsLocalDisableA20();
- if (getAX() != 1)
+ if (getAX() != 0x0001)
{
/* XmsLocalDisableA20 failed and already set AX and BL to their correct values */
break;
}
IsA20Enabled = FALSE;
+ Result = getBL();
}
setAX(0x0001); /* Line successfully disabled */
- setBL(XMS_STATUS_SUCCESS);
+ setBL(Result);
break;
}
{
setAX(XmsGetLargestFreeBlock());
setDX(FreeBlocks);
- setBL(XMS_STATUS_SUCCESS);
+
+ if (FreeBlocks > 0)
+ setBL(XMS_STATUS_SUCCESS);
+ else
+ setBL(XMS_STATUS_OUT_OF_MEMORY);
+
break;
}
case 0x09:
{
WORD Handle;
- CHAR Result = XmsAlloc(getDX(), &Handle);
+ UCHAR Result = XmsAlloc(getDX(), &Handle);
if (Result >= 0)
{
/* Free Extended Memory Block */
case 0x0A:
{
- CHAR Result = XmsFree(getDX());
+ UCHAR Result = XmsFree(getDX());
setAX(Result >= 0);
setBL(Result);
-
break;
}
{
PVOID SourceAddress, DestAddress;
PXMS_COPY_DATA CopyData = (PXMS_COPY_DATA)SEG_OFF_TO_PTR(getDS(), getSI());
+ PXMS_HANDLE HandleEntry;
if (CopyData->SourceHandle)
{
- PXMS_HANDLE Entry = GetHandleRecord(CopyData->SourceHandle);
- if (!Entry)
+ HandleEntry = GetHandleRecord(CopyData->SourceHandle);
+ if (!ValidateHandle(HandleEntry))
{
setAX(0);
setBL(XMS_STATUS_BAD_SRC_HANDLE);
break;
}
- if (CopyData->SourceOffset >= Entry->Size * XMS_BLOCK_SIZE)
+ if (CopyData->SourceOffset >= HandleEntry->Size * XMS_BLOCK_SIZE)
{
setAX(0);
setBL(XMS_STATUS_BAD_SRC_OFFSET);
}
- SourceAddress = (PVOID)REAL_TO_PHYS(Entry->Address + CopyData->SourceOffset);
+ SourceAddress = (PVOID)REAL_TO_PHYS(HandleEntry->Address + CopyData->SourceOffset);
}
else
{
/* The offset is actually a 16-bit segment:offset pointer */
- SourceAddress = SEG_OFF_TO_PTR(HIWORD(CopyData->SourceOffset),
- LOWORD(CopyData->SourceOffset));
+ SourceAddress = FAR_POINTER(CopyData->SourceOffset);
}
if (CopyData->DestHandle)
{
- PXMS_HANDLE Entry = GetHandleRecord(CopyData->DestHandle);
- if (!Entry)
+ HandleEntry = GetHandleRecord(CopyData->DestHandle);
+ if (!ValidateHandle(HandleEntry))
{
setAX(0);
setBL(XMS_STATUS_BAD_DEST_HANDLE);
break;
}
- if (CopyData->DestOffset >= Entry->Size * XMS_BLOCK_SIZE)
+ if (CopyData->DestOffset >= HandleEntry->Size * XMS_BLOCK_SIZE)
{
setAX(0);
setBL(XMS_STATUS_BAD_DEST_OFFSET);
}
- DestAddress = (PVOID)REAL_TO_PHYS(Entry->Address + CopyData->DestOffset);
+ DestAddress = (PVOID)REAL_TO_PHYS(HandleEntry->Address + CopyData->DestOffset);
}
else
{
/* The offset is actually a 16-bit segment:offset pointer */
- DestAddress = SEG_OFF_TO_PTR(HIWORD(CopyData->DestOffset),
- LOWORD(CopyData->DestOffset));
+ DestAddress = FAR_POINTER(CopyData->DestOffset);
}
+ /* Perform the move */
+ RtlMoveMemory(DestAddress, SourceAddress, CopyData->Count);
+
setAX(1);
setBL(XMS_STATUS_SUCCESS);
- RtlMoveMemory(DestAddress, SourceAddress, CopyData->Count);
break;
}
case 0x0C:
{
DWORD Address;
- CHAR Result = XmsLock(getDX(), &Address);
+ UCHAR Result = XmsLock(getDX(), &Address);
if (Result >= 0)
{
/* Unlock Extended Memory Block */
case 0x0D:
{
- CHAR Result = XmsUnlock(getDX());
+ UCHAR Result = XmsUnlock(getDX());
setAX(Result >= 0);
setBL(Result);
-
break;
}
/* Get Handle Information */
case 0x0E:
{
- PXMS_HANDLE Entry = GetHandleRecord(getDX());
-
- if (Entry && Entry->Handle != 0)
- {
- UINT i;
- UCHAR Handles = 0;
-
- for (i = 0; i < XMS_MAX_HANDLES; i++)
- {
- if (HandleTable[i].Handle == 0) Handles++;
- }
+ PXMS_HANDLE HandleEntry = GetHandleRecord(getDX());
+ UINT i;
+ UCHAR Handles = 0;
- setAX(1);
- setBH(Entry->LockCount);
- setBL(Handles);
- setDX(Entry->Size);
- }
- else
+ if (!ValidateHandle(HandleEntry))
{
setAX(0);
setBL(XMS_STATUS_INVALID_HANDLE);
+ break;
}
+ for (i = 0; i < XMS_MAX_HANDLES; i++)
+ {
+ if (HandleTable[i].Handle == 0) Handles++;
+ }
+
+ setAX(1);
+ setBH(HandleEntry->LockCount);
+ setBL(Handles);
+ setDX(HandleEntry->Size);
break;
}
- /* Allocate UMB */
- case 0x10:
+ /* Reallocate Extended Memory Block */
+ case 0x0F:
{
- WORD Segment;
- WORD MaxAvailable;
- BYTE OldAllocStrategy = Sda->AllocStrategy;
- BOOLEAN OldLinkState = DosUmbLinked;
+ PXMS_HANDLE HandleEntry = GetHandleRecord(getDX());
+ WORD NewSize = getBX();
- DosLinkUmb();
- Sda->AllocStrategy = DOS_ALLOC_HIGH | DOS_ALLOC_BEST_FIT;
- Segment = DosAllocateMemory(getDX(), &MaxAvailable);
+ if (!ValidateHandle(HandleEntry))
+ {
+ setAX(0);
+ setBL(XMS_STATUS_INVALID_HANDLE);
+ break;
+ }
- if (Segment)
+ if (HandleEntry->LockCount)
{
- setAX(1);
- setBX(Segment);
+ setAX(0);
+ setBL(XMS_STATUS_LOCKED);
+ break;
+ }
+
+ /* Check for reduction or enlargement */
+ if (NewSize < HandleEntry->Size)
+ {
+ /* Reduction, the easy case: just update the size */
+ HandleEntry->Size = NewSize;
}
else
{
+ /* Enlargement: we need to reallocate elsewhere */
+ UNIMPLEMENTED;
setAX(0);
- setBL(MaxAvailable ? XMS_STATUS_SMALLER_UMB : XMS_STATUS_OUT_OF_UMBS);
- setDX(MaxAvailable);
+ setBL(XMS_STATUS_NOT_IMPLEMENTED);
+ break;
}
- Sda->AllocStrategy = OldAllocStrategy;
- if (!OldLinkState) DosUnlinkUmb();
+ setAX(1);
+ setBL(XMS_STATUS_SUCCESS);
break;
}
- /* Free UMB */
- case 0x11:
+ /* Request UMB */
+ case 0x10:
{
- if (DosFreeMemory(getDX()))
- {
- setAX(1);
- }
+ BOOLEAN Result;
+ USHORT Segment = 0x0000; /* No preferred segment */
+ USHORT Size = getDX(); /* Size is in paragraphs */
+
+ Result = UmaDescReserve(&Segment, &Size);
+ if (Result)
+ setBX(Segment);
else
- {
- setAX(0);
+ setBL(Size > 0 ? XMS_STATUS_SMALLER_UMB : XMS_STATUS_OUT_OF_UMBS);
+
+ setDX(Size);
+ setAX(Result);
+ break;
+ }
+
+ /* Release UMB */
+ case 0x11:
+ {
+ BOOLEAN Result;
+ USHORT Segment = getDX();
+
+ Result = UmaDescRelease(Segment);
+ if (!Result)
setBL(XMS_STATUS_INVALID_UMB);
- }
+ setAX(Result);
break;
}
/* Reallocate UMB */
case 0x12:
{
- WORD Segment;
- WORD MaxAvailable;
-
- Segment = DosResizeMemory(getDX(), getBX(), &MaxAvailable);
+ BOOLEAN Result;
+ USHORT Segment = getDX();
+ USHORT Size = getBX(); /* Size is in paragraphs */
- if (Segment)
- {
- setAX(1);
- }
- else
+ Result = UmaDescReallocate(Segment, &Size);
+ if (!Result)
{
- setAX(0);
- setBL(Sda->LastErrorCode == ERROR_INVALID_HANDLE
- ? XMS_STATUS_INVALID_UMB : XMS_STATUS_SMALLER_UMB);
- setDX(MaxAvailable);
+ if (Size > 0)
+ {
+ setBL(XMS_STATUS_SMALLER_UMB);
+ setDX(Size);
+ }
+ else
+ {
+ setBL(XMS_STATUS_INVALID_UMB);
+ }
}
+ setAX(Result);
break;
}