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