3 * Expanded Memory Manager library.
4 * (C) 2009-2012 Jonathan Campbell.
5 * Hackipedia DOS library.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
11 /* api library for DOS programs that want to use Expanded Memory (usually, EMM386.EXE)
14 * This code is intended for use with 16-bit real-mode programs. 32-bit programs have whatever the DOS extender
15 * offers and have no need for expanded memory, in fact, the DOS extender will often take up all extended
16 * & expanded memory for it's use and leave us nothing, which is why 32-bit builds of this library do not
21 * YES* = Yes, if DOS underneath provides it (or if DOS, when configured to load EMM386.EXE). Otherwise, No
23 * System/configuration Works? Limit?
26 * Microsoft Windows 3.0
28 * Standard mode NO -- EMM functions present, but will always report 0KB free. If more than 16MB of RAM is present, Windows causes a serious fault and DOSBox aborts
29 * 386 Enhanced mode YES* ?
30 * Microsoft Windows 3.1
31 * Standard mode NO -- EMM functions present, but will always report 0KB free
32 * 386 Enhanced mode YES* NO
33 * Microsoft Windows 3.11
34 * Standard mode NO -- EMM functions present, but will always report 0KB free
35 * 386 Enhanced mode YES* NO
37 * Microsoft Windows 95 (4.00.950)[1]
38 * Normal mode YES* 64MB API usually reports 16MB free. The test VM had 96MB of RAM
40 * MS-DOS mode (official) YES* 32MB
41 * MS-DOS mode (gui=0) YES* 32MB
42 * Microsoft Windows 98 (4.10.1998)[1]
43 * Normal mode YES* 64MB API usually reports 16MB free. The test VM had 96MB of RAM
44 * MS-DOS mode (gui=0) YES* 32MB
45 * Microsoft Windows ME (4.90.3000)[2]
46 * Normal mode YES* 64MB The API will never report more than 16MB free, but you can hack
47 * the PIF editor for the DOS program to allow up to 65534KB of
48 * EMM memory. The test program seems to have no problem allocating
49 * 48MB of expanded memory when said hack is applied. I suppose the
50 * API could handle more, but remember limits are imposed by the
51 * DOS Box VM and those are apparently represented by unsigned
52 * 16-bit integers, thus the 64MB (65534KB) limit.
53 * MS-DOS mode (bootdisk) ? ? I am unable to get Windows ME to make a bootdisk at this time.
54 * So I attemped to use a Windows ME bootdisk from bootdisk.com,
55 * and added DEVICE=EMM386.EXE only to find that at boot time
56 * it locks up the computer! So, I have no way of knowing what
57 * a pure MS-DOS mode EMM386.EXE from Windows ME does. It probably
58 * acts just like the Windows 95/98 versions...
59 * Microsoft Windows 2000 Professional
60 * Normal mode YES 32MB For whatever reason NTVDM defaults to NOT providing EMM memory.
61 * Limits to 32MB even if you type in larger values in the PIF editor.
63 * [1] EMM386.EXE for these systems will not be able to automatically find a page frame in QEMU or VirtualBox, probably because for
64 * unmapped regions the emulator returns 0x00 not 0xFF. To work around that, open CONFIG.SYS in a text editor and edit the
65 * line referring to EMM386.EXE. Add I=E000-EFFF and save. It should look like:
67 * DEVICE=C:\WINDOWS\EMM386.EXE I=E000-EFFF.
69 * [2] You're probably wondering... if Windows ME ignores AUTOEXEC.BAT and CONFIG.SYS then how the hell do you get EMM386.EXE
70 * loaded? Well, it's very obscure and undocumented, but you can get it loaded on boot up as follows:
72 * 1. Go to the start menu, select "run" and type "notepad c:\windows\system.ini"
73 * 2. Locate the [386Enh] section, go to the bottom of the section, and add the following lines of text to the end of [386Enh]
75 * EMMInclude=E000-EFFF
76 * ReservePageFrame=yes
78 * 3. Reboot, and enjoy
80 #if !defined(TARGET_OS2) && !defined(TARGET_WINDOWS)
82 #include "src/lib/doslib/emm.h"
84 byte emm_status = 0xFF; /* initialize to 0xFF as a way of indicating that nobody checked yet */
87 byte emm_phys_pages = 0;
88 word emm_total_pages = 0;
89 unsigned int emm_page_frame_segment = 0;
90 word emm_unallocated_pages = 0;
91 struct emm_phys_page_map *emm_phys_map = NULL; /* maps physical page number -> segment address */
92 static const char *devname = "EMMXXXX0";
93 static const char *devname2 = "EMMQXXX0"; /* Microsoft publishes EMM standard then breaks it subtly in non-backwards compatible way... news at 11 */
94 #if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
95 static uint16_t emm_phys_map_sel = 0;
97 static void emm_realmode_67_call(struct dpmi_realmode_call *rc) {
102 mov edi,rc ; we trust Watcom has left ES == DS
108 void emm_phys_pages_sort() {
112 #if TARGET_MSDOS == 16 && !defined(TARGET_OS2)
113 void emm_update_page_count() {
114 emm_unallocated_pages = 0;
117 if (!emm_present) return;
124 mov emm_unallocated_pages,bx
125 mov emm_total_pages,dx
133 emmptr = (void far*)_dos_getvect(0x67);
134 if (emmptr == (void far*)0)
137 /* apparently 10 bytes into the segment there is the magic string */
138 if ( _fmemcmp((char far*)MK_FP(FP_SEG(emmptr),0x000A),(char far*)devname,8) != 0 &&
139 _fmemcmp((char far*)MK_FP(FP_SEG(emmptr),0x000A),(char far*)devname2,8) != 0)
144 emm_page_frame_segment = 0;
159 mov emm_page_frame_segment,bx
169 if (emm_phys_map != NULL) {
174 if (emm_phys_map == NULL) {
175 /* see if the EMM provides a mapping table describing the real-mode segments
176 * corresponding to each physical page. if not, then assume only one page
177 * available. the table could be up to 256 entries. the API really doesn't
178 * have a way to tell us ahead of time, so assume the worst. */
179 assert(sizeof(struct emm_phys_page_map) == (size_t)4);
180 emm_phys_map = malloc(sizeof(struct emm_phys_page_map) * 256);
181 if (emm_phys_map != NULL) {
182 const unsigned int s = FP_SEG(emm_phys_map);
183 const unsigned int o = FP_OFF(emm_phys_map);
205 void *x = realloc(emm_phys_map,sizeof(struct emm_phys_page_map) * c);
206 if (x != NULL) { /* NTS: if we cannot realloc, well, too bad */
211 /* WARNING: we are assuming several things about the table.
212 * - That the table is sorted by real-mode segment (as described in the standard)
213 * - There are no duplicate page numbers
214 * - The table has as many entries as physical pages */
216 /* do ourself a favor and sort by page number the table */
217 emm_phys_pages_sort();
225 int emm_alloc_pages(unsigned int pages) {
245 int emm_free_pages(unsigned int handle) {
265 int emm_map_page(unsigned int handle,unsigned int phys_page,unsigned int log_page) {
268 if (phys_page >= (unsigned int)emm_phys_pages)
274 mov al,byte ptr phys_page
290 /* given physical page number, return real-mode segment value */
291 word emm_last_phys_page_segment(unsigned int phys_page) {
294 if (phys_page >= (unsigned int)emm_phys_pages)
297 /* if we don't have a copy of the EMM's mapping table, then assume that there is
298 * only physical page 0 at the page frame address */
299 if (phys_page == 0 && emm_phys_map == NULL)
300 return emm_page_frame_segment;
302 for (i=0;i < emm_phys_pages && emm_phys_map != NULL;i++) {
303 struct emm_phys_page_map *me = emm_phys_map + i;
304 if (phys_page == me->number)
311 void emm_update_page_count() {
312 emm_unallocated_pages = 0;
315 if (!emm_present) return;
322 mov emm_unallocated_pages,bx
323 mov emm_total_pages,dx
327 /*int probe_emm() {//32-bit
328 unsigned int emm_seg;
332 // Tricky. The DOS extender would likely translate the vector, when what we
333 really want is the segment value of int 67h
334 emm_seg = *((uint16_t*)((0x67 << 2) + 2));
337 // apparently 10 bytes into the segment there is the magic string
338 if ( memcmp((void*)(((unsigned long)emm_seg << 4UL) + 0x000A),devname,8) != 0 &&
339 memcmp((void*)(((unsigned long)emm_seg << 4UL) + 0x000A),devname2,8) != 0)
345 emm_page_frame_segment = 0;
360 mov word ptr emm_page_frame_segment,bx
371 if (emm_phys_map != NULL) {
372 dpmi_free_dos(emm_phys_map_sel);
373 emm_phys_map_sel = 0;
377 if (emm_phys_map == NULL) {
378 // see if the EMM provides a mapping table describing the real-mode segments
379 // * corresponding to each physical page. if not, then assume only one page
380 // * available. the table could be up to 256 entries. the API really doesn't
381 // * have a way to tell us ahead of time, so assume the worst.
382 assert(sizeof(struct emm_phys_page_map) == (size_t)4);
383 emm_phys_map = dpmi_alloc_dos(sizeof(struct emm_phys_page_map) * 256,&emm_phys_map_sel);
384 if (emm_phys_map != NULL) {
385 const unsigned int s = ((uint32_t)emm_phys_map) >> 4;
386 const unsigned int o = ((uint32_t)emm_phys_map) & 0xF;
387 struct dpmi_realmode_call rc={0};
394 emm_realmode_67_call(&rc);
395 if ((rc.eax&0xFF) == 0) c = rc.ecx & 0xFFFF;
398 dpmi_free_dos(emm_phys_map_sel);
399 emm_phys_map_sel = 0;
405 // * WARNING: we are assuming several things about the table.
406 // * - That the table is sorted by real-mode segment (as described in the standard)
407 // * - There are no duplicate page numbers
408 // * - The table has as many entries as physical pages
410 // do ourself a favor and sort by page number the table
411 emm_phys_pages_sort();
419 int emm_alloc_pages(unsigned int pages) {
440 int emm_free_pages(unsigned int handle) {
460 int emm_map_page(unsigned int handle,unsigned int phys_page,unsigned int log_page) {
463 if (phys_page >= (unsigned int)emm_phys_pages)
469 mov al,byte ptr phys_page
485 word emm_last_phys_page_segment(unsigned int phys_page) {
488 if (phys_page >= (unsigned int)emm_phys_pages)
491 /* if we don't have a copy of the EMM's mapping table, then assume that there is
492 * only physical page 0 at the page frame address */
493 if (phys_page == 0 && emm_phys_map == NULL)
494 return emm_page_frame_segment;
496 for (i=0;i < emm_phys_pages && emm_phys_map != NULL;i++) {
497 struct emm_phys_page_map *me = emm_phys_map + i;
498 if (phys_page == me->number)
506 #endif /* !defined(TARGET_OS2) && !defined(TARGET_WINDOWS) */