2 * COPYRIGHT: GPLv2+ - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: DOS XMS Driver
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
17 #include "hardware/ps2.h"
24 #define XMS_DEVICE_NAME "XMSXXXX0"
29 /* PRIVATE VARIABLES **********************************************************/
31 static const BYTE EntryProcedure
[] = {
32 0xEB, // jmp short +0x03
43 static PDOS_DEVICE_NODE Node
= NULL
;
44 static XMS_HANDLE HandleTable
[XMS_MAX_HANDLES
];
45 static WORD FreeBlocks
= XMS_BLOCKS
;
46 static RTL_BITMAP AllocBitmap
;
47 static ULONG BitmapBuffer
[(XMS_BLOCKS
+ 31) / 32];
50 * Flag used by Global Enable/Disable A20 functions, so that they don't
51 * need to re-change the state of A20 if it was already enabled/disabled.
53 static BOOLEAN IsA20Enabled
= FALSE
;
55 * This flag is set to TRUE or FALSE when A20 line was already disabled or
56 * enabled when XMS driver was loaded.
57 * In case A20 was disabled, we are allowed to modify it. In case A20 was
58 * already enabled, we are not allowed to touch it.
60 static BOOLEAN CanChangeA20
= TRUE
;
62 * Count for enabling or disabling the A20 line. The A20 line is enabled
63 * only if the enabling count is greater than or equal to 0.
65 static LONG A20EnableCount
= 0;
67 /* HELPERS FOR A20 LINE *******************************************************/
69 static BOOLEAN
PCAT_A20Control(BYTE Control
, PBOOLEAN A20Status
)
71 BYTE ControllerOutput
;
73 /* Retrieve PS/2 controller output byte */
74 IOWriteB(PS2_CONTROL_PORT
, 0xD0);
75 ControllerOutput
= IOReadB(PS2_DATA_PORT
);
79 case 0: /* Disable A20 line */
80 ControllerOutput
&= ~0x02;
81 IOWriteB(PS2_CONTROL_PORT
, 0xD1);
82 IOWriteB(PS2_DATA_PORT
, ControllerOutput
);
85 case 1: /* Enable A20 line */
86 ControllerOutput
|= 0x02;
87 IOWriteB(PS2_CONTROL_PORT
, 0xD1);
88 IOWriteB(PS2_DATA_PORT
, ControllerOutput
);
91 default: /* Get A20 status */
96 *A20Status
= (ControllerOutput
& 0x02) != 0;
102 static VOID
XmsLocalEnableA20(VOID
)
104 /* Enable A20 only if we can do so, otherwise make the caller believe we enabled it */
105 if (!CanChangeA20
) goto Quit
;
107 /* The count is zero so enable A20 */
108 if (A20EnableCount
== 0 && !PCAT_A20Control(1, NULL
))
114 setAX(0x0001); /* Line successfully enabled */
115 setBL(XMS_STATUS_SUCCESS
);
119 setAX(0x0000); /* Line failed to be enabled */
120 setBL(XMS_STATUS_A20_ERROR
);
124 static VOID
XmsLocalDisableA20(VOID
)
126 /* Disable A20 only if we can do so, otherwise make the caller believe we disabled it */
127 if (!CanChangeA20
) goto Quit
;
129 /* If the count is already zero, fail */
130 if (A20EnableCount
== 0) goto Fail
;
134 /* The count is zero so disable A20 */
135 if (A20EnableCount
== 0 && !PCAT_A20Control(0, NULL
))
139 setAX(0x0001); /* Line successfully disabled */
140 setBL(XMS_STATUS_SUCCESS
);
144 setAX(0x0000); /* Line failed to be enabled */
145 setBL(XMS_STATUS_A20_ERROR
);
149 static VOID
XmsGetA20State(VOID
)
151 BOOLEAN A20Status
= FALSE
;
154 * NOTE: The XMS specification explicitely says that this check is done
155 * in a hardware-independent manner, by checking whether high memory wraps.
156 * For our purposes we just call the emulator API.
160 if (PCAT_A20Control(2, &A20Status
))
161 setBL(XMS_STATUS_SUCCESS
);
163 setBL(XMS_STATUS_A20_ERROR
);
168 /* PRIVATE FUNCTIONS **********************************************************/
170 static inline PXMS_HANDLE
GetHandleRecord(WORD Handle
)
173 if (Handle
== 0 || Handle
>= XMS_MAX_HANDLES
) return NULL
;
175 Entry
= &HandleTable
[Handle
- 1];
176 return Entry
->Size
? Entry
: NULL
;
179 static CHAR
XmsAlloc(WORD Size
, PWORD Handle
)
182 PXMS_HANDLE HandleEntry
;
184 if (Size
> FreeBlocks
) return XMS_STATUS_OUT_OF_MEMORY
;
186 for (i
= 0; i
< XMS_MAX_HANDLES
; i
++)
188 HandleEntry
= &HandleTable
[i
];
189 if (HandleEntry
->Handle
== 0)
196 if (i
== XMS_MAX_HANDLES
) return XMS_STATUS_OUT_OF_HANDLES
;
198 HandleEntry
->Handle
= i
+ 1;
199 HandleEntry
->LockCount
= 0;
200 HandleEntry
->Size
= Size
;
203 return XMS_STATUS_SUCCESS
;
206 static CHAR
XmsFree(WORD Handle
)
208 PXMS_HANDLE HandleEntry
= GetHandleRecord(Handle
);
209 if (HandleEntry
== NULL
) return XMS_STATUS_INVALID_HANDLE
;
210 if (HandleEntry
->LockCount
) return XMS_STATUS_LOCKED
;
212 HandleEntry
->Handle
= 0;
213 FreeBlocks
+= HandleEntry
->Size
;
215 return XMS_STATUS_SUCCESS
;
218 static CHAR
XmsLock(WORD Handle
, PDWORD Address
)
220 DWORD CurrentIndex
= 0;
221 PXMS_HANDLE HandleEntry
= GetHandleRecord(Handle
);
223 if (HandleEntry
== NULL
) return XMS_STATUS_INVALID_HANDLE
;
224 if (HandleEntry
->LockCount
== 0xFF) return XMS_STATUS_LOCK_OVERFLOW
;
226 if (HandleEntry
->LockCount
)
228 /* Just increment the lock count */
229 HandleEntry
->LockCount
++;
230 return XMS_STATUS_SUCCESS
;
233 while (CurrentIndex
< XMS_BLOCKS
)
236 ULONG RunSize
= RtlFindNextForwardRunClear(&AllocBitmap
, CurrentIndex
, &RunStart
);
237 if (RunSize
== 0) break;
239 if (RunSize
>= HandleEntry
->Size
)
242 HandleEntry
->LockCount
++;
243 HandleEntry
->Address
= XMS_ADDRESS
+ RunStart
* XMS_BLOCK_SIZE
;
245 RtlSetBits(&AllocBitmap
, RunStart
, RunSize
);
246 *Address
= HandleEntry
->Address
;
247 return XMS_STATUS_SUCCESS
;
251 CurrentIndex
= RunStart
+ RunSize
;
254 /* Can't find any suitable range */
255 return XMS_STATUS_CANNOT_LOCK
;
258 static CHAR
XmsUnlock(WORD Handle
)
261 PXMS_HANDLE HandleEntry
= GetHandleRecord(Handle
);
263 if (HandleEntry
== NULL
) return XMS_STATUS_INVALID_HANDLE
;
264 if (!HandleEntry
->LockCount
) return XMS_STATUS_NOT_LOCKED
;
266 /* Decrement the lock count and exit early if it's still locked */
267 if (--HandleEntry
->LockCount
) return XMS_STATUS_SUCCESS
;
269 BlockNumber
= (HandleEntry
->Address
- XMS_ADDRESS
) / XMS_BLOCK_SIZE
;
270 RtlClearBits(&AllocBitmap
, BlockNumber
, HandleEntry
->Size
);
272 return XMS_STATUS_SUCCESS
;
275 static VOID WINAPI
XmsBopProcedure(LPWORD Stack
)
279 /* Get XMS Version */
282 setAX(0x0300); /* XMS version 3.00 */
283 setBX(0x0301); /* Driver version 3.01 */
284 setDX(0x0001); /* HMA present */
288 /* Global Enable A20 */
291 /* Enable A20 if needed */
297 /* XmsLocalEnableA20 failed and already set AX and BL to their correct values */
304 setAX(0x0001); /* Line successfully enabled */
305 setBL(XMS_STATUS_SUCCESS
);
309 /* Global Disable A20 */
312 /* Disable A20 if needed */
315 XmsLocalDisableA20();
318 /* XmsLocalDisableA20 failed and already set AX and BL to their correct values */
322 IsA20Enabled
= FALSE
;
325 setAX(0x0001); /* Line successfully disabled */
326 setBL(XMS_STATUS_SUCCESS
);
330 /* Local Enable A20 */
333 /* This call sets AX and BL to their correct values */
338 /* Local Disable A20 */
341 /* This call sets AX and BL to their correct values */
342 XmsLocalDisableA20();
346 /* Query A20 State */
349 /* This call sets AX and BL to their correct values */
354 /* Query Free Extended Memory */
359 setBL(XMS_STATUS_SUCCESS
);
363 /* Allocate Extended Memory Block */
367 CHAR Result
= XmsAlloc(getDX(), &Handle
);
383 /* Free Extended Memory Block */
386 CHAR Result
= XmsFree(getDX());
394 /* Lock Extended Memory Block */
398 CHAR Result
= XmsLock(getDX(), &Address
);
404 /* Store the LINEAR address in DX:BX */
405 setDX(HIWORD(Address
));
406 setBX(LOWORD(Address
));
417 /* Unlock Extended Memory Block */
420 CHAR Result
= XmsUnlock(getDX());
430 DPRINT1("XMS command AH = 0x%02X NOT IMPLEMENTED\n", getAH());
431 setBL(XMS_STATUS_NOT_IMPLEMENTED
);
436 /* PUBLIC FUNCTIONS ***********************************************************/
438 BOOLEAN
XmsGetDriverEntry(PDWORD Pointer
)
440 if (Node
== NULL
) return FALSE
;
441 *Pointer
= DEVICE_PRIVATE_AREA(Node
->Driver
);
445 VOID
XmsInitialize(VOID
)
447 RtlZeroMemory(HandleTable
, sizeof(HandleTable
));
448 RtlZeroMemory(BitmapBuffer
, sizeof(BitmapBuffer
));
449 RtlInitializeBitMap(&AllocBitmap
, BitmapBuffer
, XMS_BLOCKS
);
451 Node
= DosCreateDeviceEx(DOS_DEVATTR_IOCTL
| DOS_DEVATTR_CHARACTER
,
453 sizeof(EntryProcedure
));
455 RegisterBop(BOP_XMS
, XmsBopProcedure
);
457 /* Copy the entry routine to the device private area */
458 RtlMoveMemory(FAR_POINTER(DEVICE_PRIVATE_AREA(Node
->Driver
)),
460 sizeof(EntryProcedure
));
463 VOID
XmsCleanup(VOID
)
465 RegisterBop(BOP_XMS
, NULL
);
466 DosDeleteDevice(Node
);