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