1 /*
2  * COPYRIGHT:       GPLv2+ - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/dos32krnl/emsdrv.c
5  * PURPOSE:         DOS EMS Driver
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  *
8  * DOCUMENTATION:   Official specification:
9  *                  LIM EMS v4.0: http://www.phatcode.net/res/218/files/limems40.txt
10  */
11 
12 /* INCLUDES *******************************************************************/
13 
14 #include "ntvdm.h"
15 
16 #define NDEBUG
17 #include <debug.h>
18 
19 #include "emulator.h"
20 #include "../../memory.h"
21 #include "bios/umamgr.h"
22 
23 #include "dos.h"
24 #include "dos/dem.h"
25 #include "device.h"
26 
27 #include "emsdrv.h"
28 
29 #define EMS_DEVICE_NAME     "EMMXXXX0"
30 
31 #define EMS_SEGMENT_SIZE    ((EMS_PHYSICAL_PAGES * EMS_PAGE_SIZE) >> 4)
32 #define EMS_SYSTEM_HANDLE   0
33 
34 /* PRIVATE VARIABLES **********************************************************/
35 
36 static PDOS_DEVICE_NODE Node;
37 static RTL_BITMAP AllocBitmap;
38 static PULONG EmsBitmapBuffer = NULL;
39 static PEMS_PAGE EmsPageTable = NULL;
40 static EMS_HANDLE EmsHandleTable[EMS_MAX_HANDLES];
41 static PVOID Mapping[EMS_PHYSICAL_PAGES] = { NULL };
42 static PVOID MappingBackup[EMS_PHYSICAL_PAGES] = { NULL };
43 static ULONG EmsTotalPages = 0;
44 static PVOID EmsMemory = NULL;
45 static USHORT EmsSegment = EMS_SEGMENT;
46 
47 /* PRIVATE FUNCTIONS **********************************************************/
48 
49 static VOID InitHandlesTable(VOID)
50 {
51     USHORT i;
52 
53     for (i = 0; i < ARRAYSIZE(EmsHandleTable); i++)
54     {
55         EmsHandleTable[i].Allocated = FALSE;
56         EmsHandleTable[i].PageCount = 0;
57         RtlZeroMemory(EmsHandleTable[i].Name, sizeof(EmsHandleTable[i].Name));
58         InitializeListHead(&EmsHandleTable[i].PageList);
59     }
60 }
61 
62 static PEMS_HANDLE CreateHandle(PUSHORT Handle)
63 {
64     PEMS_HANDLE HandleEntry;
65     USHORT i;
66 
67     /* Handle 0 is reserved (system handle) */
68     for (i = 1; i < ARRAYSIZE(EmsHandleTable); i++)
69     {
70         HandleEntry = &EmsHandleTable[i];
71         if (!HandleEntry->Allocated)
72         {
73             *Handle = i;
74             HandleEntry->Allocated = TRUE;
75             return HandleEntry;
76         }
77     }
78 
79     return NULL;
80 }
81 
82 static VOID FreeHandle(PEMS_HANDLE HandleEntry)
83 {
84     HandleEntry->Allocated = FALSE;
85     HandleEntry->PageCount = 0;
86     RtlZeroMemory(HandleEntry->Name, sizeof(HandleEntry->Name));
87     // InitializeListHead(&HandleEntry->PageList);
88 }
89 
90 static inline PEMS_HANDLE GetEmsHandleRecord(USHORT Handle)
91 {
92     if (Handle >= ARRAYSIZE(EmsHandleTable)) return NULL;
93     return &EmsHandleTable[Handle];
94 }
95 
96 static inline BOOLEAN ValidateHandle(PEMS_HANDLE HandleEntry)
97 {
98     return (HandleEntry != NULL && HandleEntry->Allocated);
99 }
100 
101 static UCHAR EmsFree(USHORT Handle)
102 {
103     PLIST_ENTRY Entry;
104     PEMS_HANDLE HandleEntry = GetEmsHandleRecord(Handle);
105 
106     if (!ValidateHandle(HandleEntry))
107         return EMS_STATUS_INVALID_HANDLE;
108 
109     for (Entry = HandleEntry->PageList.Flink;
110          Entry != &HandleEntry->PageList;
111          Entry = Entry->Flink)
112     {
113         PEMS_PAGE PageEntry = (PEMS_PAGE)CONTAINING_RECORD(Entry, EMS_PAGE, Entry);
114         ULONG PageNumber = ARRAY_INDEX(PageEntry, EmsPageTable);
115 
116         /* Free the page */
117         RtlClearBits(&AllocBitmap, PageNumber, 1);
118     }
119 
120     InitializeListHead(&HandleEntry->PageList);
121 
122     if (Handle != EMS_SYSTEM_HANDLE)
123         FreeHandle(HandleEntry);
124 
125     return EMS_STATUS_SUCCESS;
126 }
127 
128 static UCHAR EmsAlloc(USHORT NumPages, PUSHORT Handle)
129 {
130     ULONG i, CurrentIndex = 0;
131     PEMS_HANDLE HandleEntry;
132 
133     if (NumPages == 0) return EMS_STATUS_ZERO_PAGES;
134 
135     HandleEntry = CreateHandle(Handle);
136     if (!HandleEntry)  return EMS_STATUS_NO_MORE_HANDLES;
137 
138     while (HandleEntry->PageCount < NumPages)
139     {
140         ULONG RunStart;
141         ULONG RunSize = RtlFindNextForwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
142 
143         if (RunSize == 0)
144         {
145             /* Free what's been allocated already and report failure */
146             EmsFree(*Handle);
147             return EMS_STATUS_INSUFFICIENT_PAGES;
148         }
149         else if ((HandleEntry->PageCount + RunSize) > NumPages)
150         {
151             /* We don't need the entire run */
152             RunSize = NumPages - HandleEntry->PageCount;
153         }
154 
155         CurrentIndex = RunStart + RunSize;
156         HandleEntry->PageCount += RunSize;
157         RtlSetBits(&AllocBitmap, RunStart, RunSize);
158 
159         for (i = 0; i < RunSize; i++)
160         {
161             EmsPageTable[RunStart + i].Handle = *Handle;
162             InsertTailList(&HandleEntry->PageList, &EmsPageTable[RunStart + i].Entry);
163         }
164     }
165 
166     return EMS_STATUS_SUCCESS;
167 }
168 
169 static UCHAR InitSystemHandle(USHORT NumPages)
170 {
171     //
172     // FIXME: This is an adapted copy of EmsAlloc!!
173     //
174 
175     ULONG i, CurrentIndex = 0;
176     PEMS_HANDLE HandleEntry = &EmsHandleTable[EMS_SYSTEM_HANDLE];
177 
178     /* The system handle must never have been initialized before */
179     ASSERT(!HandleEntry->Allocated);
180 
181     /* Now allocate it */
182     HandleEntry->Allocated = TRUE;
183 
184     while (HandleEntry->PageCount < NumPages)
185     {
186         ULONG RunStart;
187         ULONG RunSize = RtlFindNextForwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
188 
189         if (RunSize == 0)
190         {
191             /* Free what's been allocated already and report failure */
192             EmsFree(EMS_SYSTEM_HANDLE);
193             // FIXME: For this function (and EmsAlloc as well),
194             // use instead an internal function that just uses
195             // PEMS_HANDLE pointers instead. It's only in the
196             // EMS interrupt handler that we should do the
197             // unfolding.
198             return EMS_STATUS_INSUFFICIENT_PAGES;
199         }
200         else if ((HandleEntry->PageCount + RunSize) > NumPages)
201         {
202             /* We don't need the entire run */
203             RunSize = NumPages - HandleEntry->PageCount;
204         }
205 
206         CurrentIndex = RunStart + RunSize;
207         HandleEntry->PageCount += RunSize;
208         RtlSetBits(&AllocBitmap, RunStart, RunSize);
209 
210         for (i = 0; i < RunSize; i++)
211         {
212             EmsPageTable[RunStart + i].Handle = EMS_SYSTEM_HANDLE;
213             InsertTailList(&HandleEntry->PageList, &EmsPageTable[RunStart + i].Entry);
214         }
215     }
216 
217     return EMS_STATUS_SUCCESS;
218 }
219 
220 static PEMS_PAGE GetLogicalPage(PEMS_HANDLE HandleEntry, USHORT LogicalPage)
221 {
222     PLIST_ENTRY Entry = HandleEntry->PageList.Flink;
223 
224     while (LogicalPage)
225     {
226         if (Entry == &HandleEntry->PageList) return NULL;
227         LogicalPage--;
228         Entry = Entry->Flink;
229     }
230 
231     return (PEMS_PAGE)CONTAINING_RECORD(Entry, EMS_PAGE, Entry);
232 }
233 
234 static UCHAR EmsMap(USHORT Handle, UCHAR PhysicalPage, USHORT LogicalPage)
235 {
236     PEMS_PAGE PageEntry;
237     PEMS_HANDLE HandleEntry = GetEmsHandleRecord(Handle);
238 
239     if (!ValidateHandle(HandleEntry))
240         return EMS_STATUS_INVALID_HANDLE;
241 
242     if (PhysicalPage >= EMS_PHYSICAL_PAGES)
243         return EMS_STATUS_INV_PHYSICAL_PAGE;
244 
245     if (LogicalPage == 0xFFFF)
246     {
247         /* Unmap */
248         Mapping[PhysicalPage] = NULL;
249         return EMS_STATUS_SUCCESS;
250     }
251 
252     PageEntry = GetLogicalPage(HandleEntry, LogicalPage);
253     if (!PageEntry) return EMS_STATUS_INV_LOGICAL_PAGE;
254 
255     Mapping[PhysicalPage] = (PVOID)((ULONG_PTR)EmsMemory
256                             + ARRAY_INDEX(PageEntry, EmsPageTable) * EMS_PAGE_SIZE);
257     return EMS_STATUS_SUCCESS;
258 }
259 
260 static VOID WINAPI EmsIntHandler(LPWORD Stack)
261 {
262     switch (getAH())
263     {
264         /* Get Manager Status */
265         case 0x40:
266         {
267             setAH(EMS_STATUS_SUCCESS);
268             break;
269         }
270 
271         /* Get Page Frame Segment */
272         case 0x41:
273         {
274             setAH(EMS_STATUS_SUCCESS);
275             setBX(EmsSegment);
276             break;
277         }
278 
279         /* Get Number of Unallocated Pages */
280         case 0x42:
281         {
282             setAH(EMS_STATUS_SUCCESS);
283             setBX(RtlNumberOfClearBits(&AllocBitmap));
284             setDX(EmsTotalPages);
285             break;
286         }
287 
288         /* Get Handle and Allocate Memory */
289         case 0x43:
290         {
291             USHORT Handle;
292             UCHAR Status = EmsAlloc(getBX(), &Handle);
293 
294             if (Status == EMS_STATUS_SUCCESS)
295                 setDX(Handle);
296 
297             setAH(Status);
298             break;
299         }
300 
301         /* Map Memory */
302         case 0x44:
303         {
304             setAH(EmsMap(getDX(), getAL(), getBX()));
305             break;
306         }
307 
308         /* Release Handle and Memory */
309         case 0x45:
310         {
311             setAH(EmsFree(getDX()));
312             break;
313         }
314 
315         /* Get EMM Version */
316         case 0x46:
317         {
318             setAH(EMS_STATUS_SUCCESS);
319             setAL(EMS_VERSION_NUM);
320             break;
321         }
322 
323         /* Save Page Map */
324         case 0x47:
325         {
326             // FIXME: This depends on an EMS handle given in DX
327             RtlCopyMemory(MappingBackup, Mapping, sizeof(Mapping));
328             setAH(EMS_STATUS_SUCCESS);
329             break;
330         }
331 
332         /* Restore Page Map */
333         case 0x48:
334         {
335             // FIXME: This depends on an EMS handle given in DX
336             RtlCopyMemory(Mapping, MappingBackup, sizeof(Mapping));
337             setAH(EMS_STATUS_SUCCESS);
338             break;
339         }
340 
341         /* Get Number of Opened Handles */
342         case 0x4B:
343         {
344             USHORT NumOpenHandles = 0;
345             USHORT i;
346 
347             for (i = 0; i < ARRAYSIZE(EmsHandleTable); i++)
348             {
349                 if (EmsHandleTable[i].Allocated)
350                     ++NumOpenHandles;
351             }
352 
353             setAH(EMS_STATUS_SUCCESS);
354             setBX(NumOpenHandles);
355             break;
356         }
357 
358         /* Get Handle Number of Pages */
359         case 0x4C:
360         {
361             PEMS_HANDLE HandleEntry = GetEmsHandleRecord(getDX());
362 
363             if (!ValidateHandle(HandleEntry))
364             {
365                 setAH(EMS_STATUS_INVALID_HANDLE);
366                 break;
367             }
368 
369             setAH(EMS_STATUS_SUCCESS);
370             setBX(HandleEntry->PageCount);
371             break;
372         }
373 
374         /* Get All Handles Number of Pages */
375         case 0x4D:
376         {
377             PEMS_HANDLE_PAGE_INFO HandlePageInfo = (PEMS_HANDLE_PAGE_INFO)SEG_OFF_TO_PTR(getES(), getDI());
378             USHORT NumOpenHandles = 0;
379             USHORT i;
380 
381             for (i = 0; i < ARRAYSIZE(EmsHandleTable); i++)
382             {
383                 if (EmsHandleTable[i].Allocated)
384                 {
385                     HandlePageInfo->Handle = i;
386                     HandlePageInfo->PageCount = EmsHandleTable[i].PageCount;
387                     ++HandlePageInfo;
388                     ++NumOpenHandles;
389                 }
390             }
391 
392             setAH(EMS_STATUS_SUCCESS);
393             setBX(NumOpenHandles);
394             break;
395         }
396 
397         /* Get or Set Page Map */
398         case 0x4E:
399         {
400             switch (getAL())
401             {
402                 /* Get Mapping Registers  */
403                 // case 0x00: // TODO: NOT IMPLEMENTED
404 
405                 /* Set Mapping Registers */
406                 // case 0x01: // TODO: NOT IMPLEMENTED
407 
408                 /* Get and Set Mapping Registers At Once */
409                 // case 0x02: // TODO: NOT IMPLEMENTED
410 
411                 /* Get Size of Page-Mapping Array */
412                 case 0x03:
413                 {
414                     setAH(EMS_STATUS_SUCCESS);
415                     setAL(sizeof(Mapping));
416                     break;
417                 }
418 
419                 default:
420                 {
421                     DPRINT1("EMS function AH = 0x4E, subfunction AL = %02X NOT IMPLEMENTED\n", getAL());
422                     setAH(EMS_STATUS_UNKNOWN_FUNCTION);
423                     break;
424                 }
425             }
426 
427             break;
428         }
429 
430         /* Get/Set Handle Name */
431         case 0x53:
432         {
433             PEMS_HANDLE HandleEntry = GetEmsHandleRecord(getDX());
434 
435             if (!ValidateHandle(HandleEntry))
436             {
437                 setAH(EMS_STATUS_INVALID_HANDLE);
438                 break;
439             }
440 
441             if (getAL() == 0x00)
442             {
443                 /* Retrieve the name */
444                 RtlCopyMemory(SEG_OFF_TO_PTR(getES(), getDI()),
445                               HandleEntry->Name,
446                               sizeof(HandleEntry->Name));
447                 setAH(EMS_STATUS_SUCCESS);
448             }
449             else if (getAL() == 0x01)
450             {
451                 /* Store the name */
452                 RtlCopyMemory(HandleEntry->Name,
453                               SEG_OFF_TO_PTR(getDS(), getSI()),
454                               sizeof(HandleEntry->Name));
455                 setAH(EMS_STATUS_SUCCESS);
456             }
457             else
458             {
459                 DPRINT1("Invalid subfunction %02X for EMS function AH = 53h\n", getAL());
460                 setAH(EMS_STATUS_INVALID_SUBFUNCTION);
461             }
462 
463             break;
464         }
465 
466         /* Handle Directory functions */
467         case 0x54:
468         {
469             if (getAL() == 0x00)
470             {
471                 /* Get Handle Directory */
472 
473                 PEMS_HANDLE_DIR_ENTRY HandleDir = (PEMS_HANDLE_DIR_ENTRY)SEG_OFF_TO_PTR(getES(), getDI());
474                 USHORT NumOpenHandles = 0;
475                 USHORT i;
476 
477                 for (i = 0; i < ARRAYSIZE(EmsHandleTable); i++)
478                 {
479                     if (EmsHandleTable[i].Allocated)
480                     {
481                         HandleDir->Handle = i;
482                         RtlCopyMemory(HandleDir->Name,
483                                       EmsHandleTable[i].Name,
484                                       sizeof(HandleDir->Name));
485                         ++HandleDir;
486                         ++NumOpenHandles;
487                     }
488                 }
489 
490                 setAH(EMS_STATUS_SUCCESS);
491                 setAL((UCHAR)NumOpenHandles);
492             }
493             else if (getAL() == 0x01)
494             {
495                 /* Search for Named Handle */
496 
497                 PUCHAR HandleName = (PUCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
498                 PEMS_HANDLE HandleFound = NULL;
499                 USHORT i;
500 
501                 for (i = 0; i < ARRAYSIZE(EmsHandleTable); i++)
502                 {
503                     if (EmsHandleTable[i].Allocated &&
504                         RtlCompareMemory(HandleName,
505                                          EmsHandleTable[i].Name,
506                                          sizeof(EmsHandleTable[i].Name)) == sizeof(EmsHandleTable[i].Name))
507                     {
508                         HandleFound = &EmsHandleTable[i];
509                         break;
510                     }
511                 }
512 
513                 /* Bail out if no handle was found */
514                 if (i >= ARRAYSIZE(EmsHandleTable)) // HandleFound == NULL
515                 {
516                     setAH(EMS_STATUS_HANDLE_NOT_FOUND);
517                     break;
518                 }
519 
520                 /* Return the handle number */
521                 setDX(i);
522 
523                 /* Sanity check: Check whether the handle was unnamed */
524                 i = 0;
525                 while ((i < sizeof(HandleFound->Name)) && (HandleFound->Name[i] == '\0'))
526                     ++i;
527 
528                 if (i >= sizeof(HandleFound->Name))
529                 {
530                     setAH(EMS_STATUS_UNNAMED_HANDLE);
531                 }
532                 else
533                 {
534                     setAH(EMS_STATUS_SUCCESS);
535                 }
536             }
537             else if (getAL() == 0x02)
538             {
539                 /*
540                  * Get Total Number of Handles
541                  *
542                  * This function retrieves the maximum number of handles
543                  * (allocated or not) the memory manager supports, which
544                  * a program may request.
545                  */
546                 setAH(EMS_STATUS_SUCCESS);
547                 setBX(ARRAYSIZE(EmsHandleTable));
548             }
549             else
550             {
551                 DPRINT1("Invalid subfunction %02X for EMS function AH = 54h\n", getAL());
552                 setAH(EMS_STATUS_INVALID_SUBFUNCTION);
553             }
554 
555             break;
556         }
557 
558         /* Move/Exchange Memory */
559         case 0x57:
560         {
561             PUCHAR SourcePtr, DestPtr;
562             PEMS_HANDLE HandleEntry;
563             PEMS_PAGE PageEntry;
564             BOOLEAN Exchange = getAL();
565             PEMS_COPY_DATA Data = (PEMS_COPY_DATA)SEG_OFF_TO_PTR(getDS(), getSI());
566 
567             if (Data->SourceType)
568             {
569                 /* Expanded memory */
570                 HandleEntry = GetEmsHandleRecord(Data->SourceHandle);
571                 if (!ValidateHandle(HandleEntry))
572                 {
573                     setAH(EMS_STATUS_INVALID_HANDLE);
574                     break;
575                 }
576 
577                 PageEntry = GetLogicalPage(HandleEntry, Data->SourceSegment);
578                 if (!PageEntry)
579                 {
580                     setAH(EMS_STATUS_INV_LOGICAL_PAGE);
581                     break;
582                 }
583 
584                 SourcePtr = (PUCHAR)((ULONG_PTR)EmsMemory
585                                      + ARRAY_INDEX(PageEntry, EmsPageTable) * EMS_PAGE_SIZE
586                                      + Data->SourceOffset);
587             }
588             else
589             {
590                 /* Conventional memory */
591                 SourcePtr = (PUCHAR)SEG_OFF_TO_PTR(Data->SourceSegment, Data->SourceOffset);
592             }
593 
594             if (Data->DestType)
595             {
596                 /* Expanded memory */
597                 HandleEntry = GetEmsHandleRecord(Data->DestHandle);
598                 if (!ValidateHandle(HandleEntry))
599                 {
600                     setAH(EMS_STATUS_INVALID_HANDLE);
601                     break;
602                 }
603 
604                 PageEntry = GetLogicalPage(HandleEntry, Data->DestSegment);
605                 if (!PageEntry)
606                 {
607                     setAH(EMS_STATUS_INV_LOGICAL_PAGE);
608                     break;
609                 }
610 
611                 DestPtr = (PUCHAR)((ULONG_PTR)EmsMemory
612                                    + ARRAY_INDEX(PageEntry, EmsPageTable) * EMS_PAGE_SIZE
613                                    + Data->DestOffset);
614             }
615             else
616             {
617                 /* Conventional memory */
618                 DestPtr = (PUCHAR)SEG_OFF_TO_PTR(Data->DestSegment, Data->DestOffset);
619             }
620 
621             if (Exchange)
622             {
623                 ULONG i;
624 
625                 /* Exchange */
626                 for (i = 0; i < Data->RegionLength; i++)
627                 {
628                     UCHAR Temp = DestPtr[i];
629                     DestPtr[i] = SourcePtr[i];
630                     SourcePtr[i] = Temp;
631                 }
632             }
633             else
634             {
635                 /* Move */
636                 RtlMoveMemory(DestPtr, SourcePtr, Data->RegionLength);
637             }
638 
639             setAH(EMS_STATUS_SUCCESS);
640             break;
641         }
642 
643         /* Get Mappable Physical Address Array */
644         case 0x58:
645         {
646             if (getAL() == 0x00)
647             {
648                 PEMS_MAPPABLE_PHYS_PAGE PageArray = (PEMS_MAPPABLE_PHYS_PAGE)SEG_OFF_TO_PTR(getES(), getDI());
649                 ULONG i;
650 
651                 for (i = 0; i < EMS_PHYSICAL_PAGES; i++)
652                 {
653                     PageArray->PageSegment = EMS_SEGMENT + i * (EMS_PAGE_SIZE >> 4);
654                     PageArray->PageNumber  = i;
655                     ++PageArray;
656                 }
657 
658                 setAH(EMS_STATUS_SUCCESS);
659                 setCX(EMS_PHYSICAL_PAGES);
660             }
661             else if (getAL() == 0x01)
662             {
663                 setAH(EMS_STATUS_SUCCESS);
664                 setCX(EMS_PHYSICAL_PAGES);
665             }
666             else
667             {
668                 DPRINT1("Invalid subfunction %02X for EMS function AH = 58h\n", getAL());
669                 setAH(EMS_STATUS_INVALID_SUBFUNCTION);
670             }
671 
672             break;
673         }
674 
675         /* Get Expanded Memory Hardware Information */
676         case 0x59:
677         {
678             if (getAL() == 0x00)
679             {
680                 PEMS_HARDWARE_INFO HardwareInfo = (PEMS_HARDWARE_INFO)SEG_OFF_TO_PTR(getES(), getDI());
681 
682                 /* Return the hardware information */
683                 HardwareInfo->RawPageSize         = EMS_PAGE_SIZE >> 4;
684                 HardwareInfo->AlternateRegSets    = 0;
685                 HardwareInfo->ContextAreaSize     = sizeof(Mapping);
686                 HardwareInfo->DmaRegisterSets     = 0;
687                 HardwareInfo->DmaChannelOperation = 0;
688 
689                 setAH(EMS_STATUS_SUCCESS);
690             }
691             else if (getAL() == 0x01)
692             {
693                 /* Same as function AH = 42h */
694                 setAH(EMS_STATUS_SUCCESS);
695                 setBX(RtlNumberOfClearBits(&AllocBitmap));
696                 setDX(EmsTotalPages);
697             }
698             else
699             {
700                 DPRINT1("Invalid subfunction %02X for EMS function AH = 59h\n", getAL());
701                 setAH(EMS_STATUS_INVALID_SUBFUNCTION);
702             }
703 
704             break;
705         }
706 
707         default:
708         {
709             DPRINT1("EMS function AH = %02X NOT IMPLEMENTED\n", getAH());
710             setAH(EMS_STATUS_UNKNOWN_FUNCTION);
711             break;
712         }
713     }
714 }
715 
716 static VOID FASTCALL EmsReadMemory(ULONG Address, PVOID Buffer, ULONG Size)
717 {
718     ULONG i;
719     ULONG RelativeAddress = Address - TO_LINEAR(EmsSegment, 0);
720     ULONG FirstPage = RelativeAddress / EMS_PAGE_SIZE;
721     ULONG LastPage = (RelativeAddress + Size - 1) / EMS_PAGE_SIZE;
722     ULONG Offset, Length;
723 
724     for (i = FirstPage; i <= LastPage; i++)
725     {
726         Offset = (i == FirstPage) ? RelativeAddress & (EMS_PAGE_SIZE - 1) : 0;
727         Length = ((i == LastPage)
728                  ? (RelativeAddress + Size - (LastPage << EMS_PAGE_BITS))
729                  : EMS_PAGE_SIZE) - Offset;
730 
731         if (Mapping[i]) RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Mapping[i] + Offset), Length);
732         Buffer = (PVOID)((ULONG_PTR)Buffer + Length);
733     }
734 }
735 
736 static BOOLEAN FASTCALL EmsWriteMemory(ULONG Address, PVOID Buffer, ULONG Size)
737 {
738     ULONG i;
739     ULONG RelativeAddress = Address - TO_LINEAR(EmsSegment, 0);
740     ULONG FirstPage = RelativeAddress / EMS_PAGE_SIZE;
741     ULONG LastPage = (RelativeAddress + Size - 1) / EMS_PAGE_SIZE;
742     ULONG Offset, Length;
743 
744     for (i = FirstPage; i <= LastPage; i++)
745     {
746         Offset = (i == FirstPage) ? RelativeAddress & (EMS_PAGE_SIZE - 1) : 0;
747         Length = ((i == LastPage)
748                  ? (RelativeAddress + Size - (LastPage << EMS_PAGE_BITS))
749                  : EMS_PAGE_SIZE) - Offset;
750 
751         if (Mapping[i]) RtlCopyMemory((PVOID)((ULONG_PTR)Mapping[i] + Offset), Buffer, Length);
752         Buffer = (PVOID)((ULONG_PTR)Buffer + Length);
753     }
754 
755     return TRUE;
756 }
757 
758 static WORD NTAPI EmsDrvDispatchIoctlRead(PDOS_DEVICE_NODE Device, DWORD Buffer, PWORD Length)
759 {
760     // TODO: NOT IMPLEMENTED
761     UNIMPLEMENTED;
762     return DOS_DEVSTAT_DONE;
763 }
764 
765 /* PUBLIC FUNCTIONS ***********************************************************/
766 
767 BOOLEAN EmsDrvInitialize(USHORT Segment, ULONG TotalPages)
768 {
769     USHORT Size;
770 
771     /* Try to allocate our page table in UMA at the given segment */
772     EmsSegment = (Segment != 0 ? Segment : EMS_SEGMENT);
773     Size = EMS_SEGMENT_SIZE; // Size in paragraphs
774     if (!UmaDescReserve(&EmsSegment, &Size)) return FALSE;
775 
776     EmsTotalPages = TotalPages;
777     EmsBitmapBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
778                                       HEAP_ZERO_MEMORY,
779                                       ((TotalPages + 31) / 32) * sizeof(ULONG));
780     if (EmsBitmapBuffer == NULL)
781     {
782         UmaDescRelease(EmsSegment);
783         return FALSE;
784     }
785 
786     RtlInitializeBitMap(&AllocBitmap, EmsBitmapBuffer, TotalPages);
787 
788     EmsPageTable = (PEMS_PAGE)RtlAllocateHeap(RtlGetProcessHeap(),
789                                               HEAP_ZERO_MEMORY,
790                                               TotalPages * sizeof(EMS_PAGE));
791     if (EmsPageTable == NULL)
792     {
793         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsBitmapBuffer);
794         EmsBitmapBuffer = NULL;
795 
796         UmaDescRelease(EmsSegment);
797         return FALSE;
798     }
799 
800     EmsMemory = (PVOID)RtlAllocateHeap(RtlGetProcessHeap(), 0, TotalPages * EMS_PAGE_SIZE);
801     if (EmsMemory == NULL)
802     {
803         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsPageTable);
804         EmsPageTable = NULL;
805         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsBitmapBuffer);
806         EmsBitmapBuffer = NULL;
807 
808         UmaDescRelease(EmsSegment);
809         return FALSE;
810     }
811 
812     InitHandlesTable();
813     /*
814      * FIXME: We should ensure that the system handle is associated
815      * with mapped pages from conventional memory. DosEmu seems to do
816      * it correctly. 384kB of memory mapped.
817      */
818     if (InitSystemHandle(384/16) != EMS_STATUS_SUCCESS)
819     {
820         DPRINT1("Impossible to allocate pages for the system handle!\n");
821 
822         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsMemory);
823         EmsMemory = NULL;
824         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsPageTable);
825         EmsPageTable = NULL;
826         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsBitmapBuffer);
827         EmsBitmapBuffer = NULL;
828 
829         UmaDescRelease(EmsSegment);
830         return FALSE;
831     }
832 
833     MemInstallFastMemoryHook(UlongToPtr(TO_LINEAR(EmsSegment, 0)),
834                              EMS_PHYSICAL_PAGES * EMS_PAGE_SIZE,
835                              EmsReadMemory,
836                              EmsWriteMemory);
837 
838     /* Create the device */
839     Node = DosCreateDeviceEx(DOS_DEVATTR_IOCTL | DOS_DEVATTR_CHARACTER,
840                              EMS_DEVICE_NAME,
841                              Int16To32StubSize);
842     Node->IoctlReadRoutine = EmsDrvDispatchIoctlRead;
843 
844     RegisterInt32(DEVICE_PRIVATE_AREA(Node->Driver),
845                   EMS_INTERRUPT_NUM, EmsIntHandler, NULL);
846 
847     return TRUE;
848 }
849 
850 VOID EmsDrvCleanup(VOID)
851 {
852     /* Delete the device */
853     DosDeleteDevice(Node);
854 
855     MemRemoveFastMemoryHook(UlongToPtr(TO_LINEAR(EmsSegment, 0)),
856                             EMS_PHYSICAL_PAGES * EMS_PAGE_SIZE);
857 
858     if (EmsMemory)
859     {
860         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsMemory);
861         EmsMemory = NULL;
862     }
863 
864     if (EmsPageTable)
865     {
866         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsPageTable);
867         EmsPageTable = NULL;
868     }
869 
870     if (EmsBitmapBuffer)
871     {
872         RtlFreeHeap(RtlGetProcessHeap(), 0, EmsBitmapBuffer);
873         EmsBitmapBuffer = NULL;
874     }
875 
876     UmaDescRelease(EmsSegment);
877 }
878