DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/ints/xms.cpp
00001 /*
00002  *  Copyright (C) 2002-2020  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 
00020 #include <stdlib.h>
00021 #include <string.h>
00022 #include <stddef.h>
00023 #include "dosbox.h"
00024 #include "callback.h"
00025 #include "mem.h"
00026 #include "regs.h"
00027 #include "dos_inc.h"
00028 #include "setup.h"
00029 #include "inout.h"
00030 #include "xms.h"
00031 #include "bios.h"
00032 #include "cpu.h"
00033 #include "control.h"
00034 
00035 #include <algorithm>
00036 
00037 #define XMS_HANDLES_MIN                     4u
00038 #define XMS_HANDLES_MAX                                         256u    /* 256 XMS Memory Blocks */ 
00039 #define XMS_HANDLES_DEFAULT                 50u     /* DOSBox SVN default */
00040 
00041 unsigned int XMS_HANDLES =                  XMS_HANDLES_DEFAULT;
00042 
00043 #define XMS_VERSION                                             0x0300u /* version 3.00 */
00044 #define XMS_DRIVER_VERSION                                      0x0301u /* my driver version 3.01 */
00045 
00046 #define XMS_GET_VERSION                                         0x00u
00047 #define XMS_ALLOCATE_HIGH_MEMORY                        0x01u
00048 #define XMS_FREE_HIGH_MEMORY                            0x02u
00049 #define XMS_GLOBAL_ENABLE_A20                           0x03u
00050 #define XMS_GLOBAL_DISABLE_A20                          0x04u
00051 #define XMS_LOCAL_ENABLE_A20                            0x05u
00052 #define XMS_LOCAL_DISABLE_A20                           0x06u
00053 #define XMS_QUERY_A20                                           0x07u
00054 #define XMS_QUERY_FREE_EXTENDED_MEMORY          0x08u
00055 #define XMS_ALLOCATE_EXTENDED_MEMORY            0x09u
00056 #define XMS_FREE_EXTENDED_MEMORY                        0x0au
00057 #define XMS_MOVE_EXTENDED_MEMORY_BLOCK          0x0bu
00058 #define XMS_LOCK_EXTENDED_MEMORY_BLOCK          0x0cu
00059 #define XMS_UNLOCK_EXTENDED_MEMORY_BLOCK        0x0du
00060 #define XMS_GET_EMB_HANDLE_INFORMATION          0x0eu
00061 #define XMS_RESIZE_EXTENDED_MEMORY_BLOCK        0x0fu
00062 #define XMS_ALLOCATE_UMB                                        0x10u
00063 #define XMS_DEALLOCATE_UMB                                      0x11u
00064 #define XMS_QUERY_ANY_FREE_MEMORY                       0x88u
00065 #define XMS_ALLOCATE_ANY_MEMORY                         0x89u
00066 #define XMS_GET_EMB_HANDLE_INFORMATION_EXT      0x8eu
00067 #define XMS_RESIZE_ANY_EXTENDED_MEMORY_BLOCK 0x8fu
00068 
00069 #define XMS_FUNCTION_NOT_IMPLEMENTED            0x80u
00070 #define HIGH_MEMORY_NOT_EXIST                           0x90u
00071 #define HIGH_MEMORY_IN_USE                                      0x91u
00072 #define HIGH_MEMORY_NOT_BIG_ENOUGH                      0x92u
00073 #define HIGH_MEMORY_NOT_ALLOCATED                       0x93u
00074 #define XMS_OUT_OF_SPACE                                        0xa0u
00075 #define XMS_OUT_OF_HANDLES                                      0xa1u
00076 #define XMS_INVALID_HANDLE                                      0xa2u
00077 #define XMS_INVALID_SOURCE_HANDLE                       0xa3u
00078 #define XMS_INVALID_SOURCE_OFFSET                       0xa4u
00079 #define XMS_INVALID_DEST_HANDLE                         0xa5u
00080 #define XMS_INVALID_DEST_OFFSET                         0xa6u
00081 #define XMS_INVALID_LENGTH                                      0xa7u
00082 #define XMS_BLOCK_NOT_LOCKED                            0xaau
00083 #define XMS_BLOCK_LOCKED                                        0xabu
00084 #define UMB_ONLY_SMALLER_BLOCK                          0xb0u
00085 #define UMB_NO_BLOCKS_AVAILABLE                         0xb1u
00086 
00087 bool DOS_IS_IN_HMA();
00088 
00089 extern Bitu rombios_minimum_location;
00090 extern bool dos_umb;
00091 
00092 Bitu xms_hma_minimum_alloc = 0;
00093 bool xms_hma_exists = true;
00094 bool xms_hma_application_has_control = false;
00095 bool xms_hma_alloc_non_dos_kernel_control = true;
00096 
00097 struct XMS_Block {
00098         Bitu    size;
00099         MemHandle mem;
00100         Bit8u   locked;
00101         bool    free;
00102 };
00103 
00104 #ifdef _MSC_VER
00105 #pragma pack (1)
00106 #endif
00107 struct XMS_MemMove{
00108         Bit32u length;
00109         Bit16u src_handle;
00110         union {
00111                 RealPt realpt;
00112                 Bit32u offset;
00113         } src;
00114         Bit16u dest_handle;
00115         union {
00116                 RealPt realpt;
00117                 Bit32u offset;
00118         } dest;
00119 
00120 } GCC_ATTRIBUTE(packed);
00121 #ifdef _MSC_VER
00122 #pragma pack ()
00123 #endif
00124 
00125 static bool xms_global_enable = false;
00126 static int xms_local_enable_count = 0;
00127 
00128 void DOS_Write_HMA_CPM_jmp(void);
00129 
00130 Bitu XMS_EnableA20(bool enable) {
00131     if (IS_PC98_ARCH) {
00132         // NEC PC-98: Unmask (enable) A20 by writing to port 0xF2.
00133         //            Mask (disable) A20 by writing to port 0xF6.
00134         IO_Write(0xF6,enable ? 0x02 : 0x03); /* 0000 001x  x = mask A20 */
00135     }
00136     else {
00137         // IBM PC/AT: Port 0x92, bit 1, set if A20 enabled
00138         Bit8u val = IO_Read(0x92);
00139         if (enable) IO_Write(0x92,val | 2);
00140         else            IO_Write(0x92,val & ~2);
00141     }
00142 
00143         return 0;
00144 }
00145 
00146 Bitu XMS_GetEnabledA20(void) {
00147     if (IS_PC98_ARCH) // NEC PC-98: Port 0xF2, bit 0, cleared if A20 enabled (set if A20 masked)
00148             return (IO_Read(0xF2)&1) == 0;
00149 
00150     // IBM PC/AT: Port 0x92, bit 1, set if A20 enabled
00151         return (IO_Read(0x92)&2)>0;
00152 }
00153 
00154 static RealPt xms_callback;
00155 static bool umb_available = false;
00156 static bool umb_init = false;
00157 
00158 static XMS_Block xms_handles[XMS_HANDLES_MAX];
00159 
00160 Bitu XMS_GetTotalHandles(void) {
00161     return XMS_HANDLES;
00162 }
00163 
00164 bool XMS_GetHandleInfo(Bitu &phys_location,Bitu &size,Bitu &lockcount,bool &free,Bitu handle) {
00165     if (handle != 0 && handle < XMS_HANDLES) {
00166         phys_location = 0;
00167         lockcount = 0;
00168         free = true;
00169         size = 0;
00170 
00171         auto &x = xms_handles[handle];
00172 
00173         if (!x.free) {
00174             free = false;
00175             size = x.size;
00176             lockcount = x.locked;
00177             phys_location = (unsigned long)x.mem << 12UL;
00178         }
00179 
00180         return true;
00181     }
00182 
00183     return false;
00184 }
00185 
00186 static INLINE bool InvalidHandle(Bitu handle) {
00187         return (!handle || (handle>=XMS_HANDLES) || xms_handles[handle].free);
00188 }
00189 
00190 Bitu XMS_QueryFreeMemory(Bit32u& largestFree, Bit32u& totalFree) {
00191         /* Scan the tree for free memory and find largest free block */
00192         totalFree=(Bit32u)(MEM_FreeTotal()*4);
00193         largestFree=(Bit32u)(MEM_FreeLargest()*4);
00194         if (!totalFree) return XMS_OUT_OF_SPACE;
00195         return 0;
00196 }
00197 
00198 void XMS_ZeroAllocation(MemHandle mem,unsigned int pages) {
00199         PhysPt address;
00200 
00201         if (pages == 0) return;
00202         address = (PhysPt)mem * (PhysPt)4096UL;
00203         pages *= 4096UL;
00204 
00205         if ((address+pages) > 0xC0000000UL) E_Exit("XMS_ZeroAllocation out of range");
00206         while (pages != 0) {
00207                 mem_writeb(address++,0);
00208                 pages--;
00209         }
00210 }
00211 
00212 extern bool enable_a20_on_windows_init;
00213 extern bool dbg_zero_on_xms_allocmem;
00214 
00215 Bitu XMS_AllocateMemory(Bitu size, Bit16u& handle) {    // size = kb
00216         /* Find free handle */
00217         Bit16u index=1;
00218         while (!xms_handles[index].free) {
00219                 if (++index>=XMS_HANDLES) return XMS_OUT_OF_HANDLES;
00220         }
00221         MemHandle mem;
00222         if (size!=0) {
00223                 Bitu pages=(size/4) + ((size & 3) ? 1 : 0);
00224                 mem=MEM_AllocatePages(pages,true);
00225                 if (!mem) return XMS_OUT_OF_SPACE;
00226                 if (dbg_zero_on_xms_allocmem) XMS_ZeroAllocation(mem,(unsigned int)pages);
00227         } else {
00228                 mem=MEM_GetNextFreePage();
00229                 if (mem==0) LOG(LOG_MISC,LOG_DEBUG)("XMS:Allocate zero pages with no memory left"); // Windows 3.1 triggers this surprisingly often!
00230                 if (mem != 0 && dbg_zero_on_xms_allocmem) XMS_ZeroAllocation(mem,1);
00231         }
00232         xms_handles[index].free=false;
00233         xms_handles[index].mem=mem;
00234         xms_handles[index].locked=0;
00235         xms_handles[index].size=size;
00236         handle=index;
00237         return 0;
00238 }
00239 
00240 Bitu XMS_FreeMemory(Bitu handle) {
00241         if (InvalidHandle(handle)) return XMS_INVALID_HANDLE;
00242     if (xms_handles[handle].locked != 0) return XMS_BLOCK_LOCKED;
00243         MEM_ReleasePages(xms_handles[handle].mem);
00244         xms_handles[handle].mem=-1;
00245         xms_handles[handle].size=0;
00246         xms_handles[handle].free=true;
00247         return 0;
00248 }
00249 
00250 Bitu XMS_MoveMemory(PhysPt bpt) {
00251         /* Read the block with mem_read's */
00252         Bitu length=mem_readd(bpt+offsetof(XMS_MemMove,length));
00253 
00254     /* "Length must be even" --Microsoft XMS Spec 3.0 */
00255     if (length & 1u) return XMS_INVALID_LENGTH;
00256 
00257         Bitu src_handle=mem_readw(bpt+offsetof(XMS_MemMove,src_handle));
00258         union {
00259                 RealPt realpt;
00260                 Bit32u offset;
00261         } src,dest;
00262         src.offset=mem_readd(bpt+offsetof(XMS_MemMove,src.offset));
00263         Bitu dest_handle=mem_readw(bpt+offsetof(XMS_MemMove,dest_handle));
00264         dest.offset=mem_readd(bpt+offsetof(XMS_MemMove,dest.offset));
00265         PhysPt srcpt,destpt;
00266         if (src_handle) {
00267                 if (InvalidHandle(src_handle)) {
00268                         return XMS_INVALID_SOURCE_HANDLE;
00269                 }
00270                 if (src.offset>=(xms_handles[src_handle].size*1024U)) {
00271                         return XMS_INVALID_SOURCE_OFFSET;
00272                 }
00273                 if (length>xms_handles[src_handle].size*1024U-src.offset) {
00274                         return XMS_INVALID_LENGTH;
00275                 }
00276                 srcpt=((unsigned int)xms_handles[src_handle].mem*4096U)+src.offset;
00277         } else {
00278                 srcpt=Real2Phys(src.realpt);
00279 
00280         /* Microsoft TEST.C considers it an error to allow real mode pointers + length to
00281          * extend past the end of the 8086-accessible conventional memory area. */
00282         if ((srcpt+length) > 0x10FFF0u) return XMS_INVALID_LENGTH;
00283         }
00284         if (dest_handle) {
00285                 if (InvalidHandle(dest_handle)) {
00286                         return XMS_INVALID_DEST_HANDLE;
00287                 }
00288                 if (dest.offset>=(xms_handles[dest_handle].size*1024U)) {
00289                         return XMS_INVALID_DEST_OFFSET;
00290                 }
00291                 if (length>xms_handles[dest_handle].size*1024U-dest.offset) {
00292                         return XMS_INVALID_LENGTH;
00293                 }
00294                 destpt=((unsigned int)xms_handles[dest_handle].mem*4096U)+dest.offset;
00295         } else {
00296                 destpt=Real2Phys(dest.realpt);
00297 
00298         /* Microsoft TEST.C considers it an error to allow real mode pointers + length to
00299          * extend past the end of the 8086-accessible conventional memory area. */
00300         if ((destpt+length) > 0x10FFF0u) return XMS_INVALID_LENGTH;
00301         }
00302 //      LOG_MSG("XMS move src %X dest %X length %X",srcpt,destpt,length);
00303 
00304     /* we must enable the A20 gate during this copy.
00305      * DOSBox-X masks the A20 line and this will only cause corruption otherwise. */
00306 
00307     if (length != 0) {
00308         bool a20_was_enabled = XMS_GetEnabledA20();
00309 
00310         xms_local_enable_count++;
00311         XMS_EnableA20(true);
00312 
00313         mem_memcpy(destpt,srcpt,length);
00314 
00315         xms_local_enable_count--;
00316         if (!a20_was_enabled) XMS_EnableA20(false);
00317     }
00318 
00319     return 0;
00320 }
00321 
00322 Bitu XMS_LockMemory(Bitu handle, Bit32u& address) {
00323         if (InvalidHandle(handle)) return XMS_INVALID_HANDLE;
00324         if (xms_handles[handle].locked<255) xms_handles[handle].locked++;
00325         address = (unsigned long)xms_handles[handle].mem * 4096UL;
00326         return 0;
00327 }
00328 
00329 Bitu XMS_UnlockMemory(Bitu handle) {
00330         if (InvalidHandle(handle)) return XMS_INVALID_HANDLE;
00331         if (xms_handles[handle].locked) {
00332                 xms_handles[handle].locked--;
00333                 return 0;
00334         }
00335         return XMS_BLOCK_NOT_LOCKED;
00336 }
00337 
00338 Bitu XMS_GetHandleInformation(Bitu handle, Bit8u& lockCount, Bit8u& numFree, Bit32u& size) {
00339         if (InvalidHandle(handle)) return XMS_INVALID_HANDLE;
00340         lockCount = xms_handles[handle].locked;
00341         /* Find available blocks */
00342         numFree=0;
00343         for (Bitu i=1;i<XMS_HANDLES;i++) {
00344                 if (xms_handles[i].free) numFree++;
00345         }
00346         size=(Bit32u)(xms_handles[handle].size);
00347         return 0;
00348 }
00349 
00350 Bitu XMS_ResizeMemory(Bitu handle, Bitu newSize) {
00351         if (InvalidHandle(handle)) return XMS_INVALID_HANDLE;   
00352         // Block has to be unlocked
00353         if (xms_handles[handle].locked>0) return XMS_BLOCK_LOCKED;
00354         Bitu pages=newSize/4 + ((newSize & 3) ? 1 : 0);
00355         if (MEM_ReAllocatePages(xms_handles[handle].mem,pages,true)) {
00356                 xms_handles[handle].size = newSize;
00357                 return 0;
00358         } else return XMS_OUT_OF_SPACE;
00359 }
00360 
00361 static bool multiplex_xms(void) {
00362         switch (reg_ax) {
00363         case 0x4300:                                    /* XMS installed check */
00364                         reg_al=0x80;
00365                         return true;
00366         case 0x4310:                                    /* XMS handler seg:offset */
00367                         SegSet16(es,RealSeg(xms_callback));
00368                         reg_bx=RealOff(xms_callback);
00369                         return true;                    
00370         }
00371         return false;
00372 
00373 }
00374 
00375 INLINE void SET_RESULT(Bitu res,bool touch_bl_on_succes=true) {
00376         if(touch_bl_on_succes || res) reg_bl = (Bit8u)res;
00377         reg_ax = (res==0)?1:0;
00378 }
00379 
00380 Bitu XMS_LocalEnableA20(void) {
00381     /* This appears to be how Microsoft HIMEM.SYS implements this. A20 is only enabled if the local enable count was == 0 at entry to this call. */
00382     if ((xms_local_enable_count++) == 0)
00383         XMS_EnableA20(true);
00384 
00385     return 0;
00386 }
00387 
00388 Bitu XMS_LocalDisableA20(void) {
00389     /* This appears to be how Microsoft HIMEM.SYS implements this. A20 is only disabled if the local enable count was == 1 at entry to this call. */
00390     if (xms_local_enable_count > 0) {
00391         if (--xms_local_enable_count == 0)
00392             XMS_EnableA20(false);
00393     }
00394     else {
00395         return 0x82; // A20 error (HIMEM.SYS behavior)
00396     }
00397 
00398     return 0;
00399 }
00400 
00401 void XMS_DOS_LocalA20EnableIfNotEnabled(void) {
00402     /* Confirmed MS-DOS behavior if DOS=HIGH */
00403     if (!XMS_GetEnabledA20()) {
00404         LOG(LOG_DOSMISC,LOG_DEBUG)("DOS=HIGH, XMS enabled, A20 gate disabled. Reenabling A20 gate on INT 21h call.");
00405         XMS_LocalEnableA20();
00406     }
00407 }
00408 
00409 Bitu XMS_Handler(void) {
00410     Bitu r;
00411 
00412 //      LOG(LOG_MISC,LOG_ERROR)("XMS: CALL %02X",reg_ah);
00413         switch (reg_ah) {
00414         case XMS_GET_VERSION:                                                                           /* 00 */
00415                 reg_ax=XMS_VERSION;
00416                 reg_bx=XMS_DRIVER_VERSION;
00417                 reg_dx=xms_hma_exists?1:0;
00418                 break;
00419         case XMS_ALLOCATE_HIGH_MEMORY:                                                          /* 01 */
00420                 if (xms_hma_exists) {
00421                         if (xms_hma_application_has_control || DOS_IS_IN_HMA()) {
00422                                 /* hma already controlled by application or DOS kernel */
00423                                 reg_ax=0;
00424                                 reg_bl=HIGH_MEMORY_IN_USE;
00425                         }
00426                         else if (reg_dx < xms_hma_minimum_alloc) {
00427                                 /* not big enough */
00428                                 reg_ax=0;
00429                                 reg_bl=HIGH_MEMORY_NOT_BIG_ENOUGH;
00430                         }
00431                         else { /* program allocation */
00432                                 LOG(LOG_MISC,LOG_DEBUG)("XMS: HMA allocated by application/TSR");
00433                                 xms_hma_application_has_control = true;
00434                                 reg_ax=1;
00435                         }
00436                 }
00437                 else {
00438                         reg_ax=0;
00439                         reg_bl=HIGH_MEMORY_NOT_EXIST;
00440                 }
00441                 break;
00442         case XMS_FREE_HIGH_MEMORY:                                                                      /* 02 */
00443                 if (xms_hma_exists) {
00444                         if (DOS_IS_IN_HMA()) LOG(LOG_MISC,LOG_WARN)("DOS application attempted to free HMA while DOS kernel occupies it!");
00445 
00446                         if (xms_hma_application_has_control) {
00447                                 LOG(LOG_MISC,LOG_DEBUG)("XMS: HMA freed by application/TSR");
00448                                 xms_hma_application_has_control = false;
00449                                 reg_ax=1;
00450                         }
00451                         else {
00452                                 reg_ax=0;
00453                                 reg_bl=HIGH_MEMORY_NOT_ALLOCATED;
00454                         }
00455                 }
00456                 else {
00457                         reg_ax=0;
00458                         reg_bl=HIGH_MEMORY_NOT_EXIST;
00459                 }
00460                 break;
00461         case XMS_GLOBAL_ENABLE_A20:                                                                     /* 03 */
00462         /* This appears to be how Microsoft HIMEM.SYS implements this */
00463         if (!xms_global_enable) {
00464             if ((r=XMS_LocalEnableA20()) == 0)
00465                 xms_global_enable = true;
00466         }
00467         else {
00468             r = 0;
00469         }
00470         SET_RESULT(r);
00471                 break;
00472         case XMS_GLOBAL_DISABLE_A20:                                                            /* 04 */
00473         /* This appears to be how Microsoft HIMEM.SYS implements this */
00474         if (xms_global_enable) {
00475             if ((r=XMS_LocalDisableA20()) == 0)
00476                 xms_global_enable = false;
00477         }
00478         else {
00479             r = 0;
00480         }
00481         SET_RESULT(r);
00482         break;
00483     case XMS_LOCAL_ENABLE_A20:                                                                  /* 05 */
00484         SET_RESULT(XMS_LocalEnableA20());
00485         break;
00486         case XMS_LOCAL_DISABLE_A20:                                                                     /* 06 */
00487         SET_RESULT(XMS_LocalDisableA20());
00488                 break;
00489         case XMS_QUERY_A20:                                                                                     /* 07 */
00490                 reg_ax = (Bit16u)XMS_GetEnabledA20();
00491                 reg_bl = 0;
00492                 break;
00493         case XMS_QUERY_FREE_EXTENDED_MEMORY:                                            /* 08 */
00494                 reg_bl = (Bit8u)XMS_QueryFreeMemory(reg_eax,reg_edx);
00495                 if (reg_eax > 65535) reg_eax = 65535; /* cap sizes for older DOS programs. newer ones use function 0x88 */
00496                 if (reg_edx > 65535) reg_edx = 65535;
00497                 break;
00498         case XMS_ALLOCATE_ANY_MEMORY:                                                           /* 89 */
00499                 { /* Chopping off bits 16-31 to fall through to ALLOCATE_EXTENDED_MEMORY is inaccurate.
00500                      The Extended Memory Specification states you use all of EDX, so programs can request
00501                      64MB or more. Even if DOSBox does not (yet) support >= 64MB of RAM. */
00502                 Bit16u handle = 0;
00503                 SET_RESULT(XMS_AllocateMemory(reg_edx,handle));
00504                 reg_dx = handle;
00505                 } break;
00506         case XMS_ALLOCATE_EXTENDED_MEMORY:                                                      /* 09 */
00507                 {
00508                 Bit16u handle = 0;
00509                 SET_RESULT(XMS_AllocateMemory(reg_dx,handle));
00510                 reg_dx = handle;
00511                 } break;
00512         case XMS_FREE_EXTENDED_MEMORY:                                                          /* 0a */
00513                 SET_RESULT(XMS_FreeMemory(reg_dx));
00514                 break;
00515         case XMS_MOVE_EXTENDED_MEMORY_BLOCK:                                            /* 0b */
00516                 SET_RESULT(XMS_MoveMemory(SegPhys(ds)+reg_si),false);
00517                 break;
00518         case XMS_LOCK_EXTENDED_MEMORY_BLOCK: {                                          /* 0c */
00519                 Bit32u address;
00520                 Bitu res = XMS_LockMemory(reg_dx, address);
00521                 if(res) reg_bl = (Bit8u)res;
00522                 reg_ax = (res==0);
00523                 if (res==0) { // success
00524                         reg_bx=(Bit16u)(address & 0xFFFF);
00525                         reg_dx=(Bit16u)(address >> 16);
00526                 }
00527                 } break;
00528         case XMS_UNLOCK_EXTENDED_MEMORY_BLOCK:                                          /* 0d */
00529                 SET_RESULT(XMS_UnlockMemory(reg_dx));
00530                 break;
00531         case XMS_GET_EMB_HANDLE_INFORMATION:                                            /* 0e */
00532                 SET_RESULT(XMS_GetHandleInformation(reg_dx,reg_bh,reg_bl,reg_edx),false);
00533                 reg_edx &= 0xFFFF;
00534                 break;
00535         case XMS_RESIZE_ANY_EXTENDED_MEMORY_BLOCK:                                      /* 0x8f */
00536                 SET_RESULT(XMS_ResizeMemory(reg_dx, reg_ebx));
00537                 break;
00538         case XMS_RESIZE_EXTENDED_MEMORY_BLOCK:                                          /* 0f */
00539                 SET_RESULT(XMS_ResizeMemory(reg_dx, reg_bx));
00540                 break;
00541         case XMS_ALLOCATE_UMB: {                                                                        /* 10 */
00542                 if (!umb_available) {
00543                         reg_ax=0;
00544                         reg_bl=XMS_FUNCTION_NOT_IMPLEMENTED;
00545                         break;
00546                 }
00547                 Bit16u umb_start=dos_infoblock.GetStartOfUMBChain();
00548                 if (umb_start==0xffff) {
00549                         reg_ax=0;
00550                         reg_bl=UMB_NO_BLOCKS_AVAILABLE;
00551                         reg_dx=0;       // no upper memory available
00552                         break;
00553                 }
00554                 /* Save status and linkage of upper UMB chain and link upper
00555                    memory to the regular MCB chain */
00556                 Bit8u umb_flag=dos_infoblock.GetUMBChainState();
00557                 if ((umb_flag&1)==0) DOS_LinkUMBsToMemChain(1);
00558                 Bit8u old_memstrat=DOS_GetMemAllocStrategy()&0xff;
00559                 DOS_SetMemAllocStrategy(0x40);  // search in UMBs only
00560 
00561                 Bit16u size=reg_dx;Bit16u seg;
00562                 if (DOS_AllocateMemory(&seg,&size)) {
00563                         reg_ax=1;
00564                         reg_bx=seg;
00565                 } else {
00566                         reg_ax=0;
00567                         if (size==0) reg_bl=UMB_NO_BLOCKS_AVAILABLE;
00568                         else reg_bl=UMB_ONLY_SMALLER_BLOCK;
00569                         reg_dx=size;    // size of largest available UMB
00570                 }
00571 
00572                 /* Restore status and linkage of upper UMB chain */
00573                 Bit8u current_umb_flag=dos_infoblock.GetUMBChainState();
00574                 if ((current_umb_flag&1)!=(umb_flag&1)) DOS_LinkUMBsToMemChain(umb_flag);
00575                 DOS_SetMemAllocStrategy(old_memstrat);
00576                 }
00577                 break;
00578         case XMS_DEALLOCATE_UMB:                                                                        /* 11 */
00579                 if (!umb_available) {
00580                         reg_ax=0;
00581                         reg_bl=XMS_FUNCTION_NOT_IMPLEMENTED;
00582                         break;
00583                 }
00584                 if (dos_infoblock.GetStartOfUMBChain()!=0xffff) {
00585                         if (DOS_FreeMemory(reg_dx)) {
00586                                 reg_ax=0x0001;
00587                                 break;
00588                         }
00589                 }
00590                 reg_ax=0x0000;
00591                 reg_bl=UMB_NO_BLOCKS_AVAILABLE;
00592                 break;
00593         case XMS_QUERY_ANY_FREE_MEMORY:                                                         /* 88 */
00594                 reg_bl = (Bit8u)XMS_QueryFreeMemory(reg_eax,reg_edx);
00595                 reg_ecx = (Bit32u)((MEM_TotalPages()*MEM_PAGESIZE)-1);                  // highest known physical memory address
00596                 break;
00597         case XMS_GET_EMB_HANDLE_INFORMATION_EXT: {                                      /* 8e */
00598                 Bit8u free_handles;
00599                 Bitu result = XMS_GetHandleInformation(reg_dx,reg_bh,free_handles,reg_edx);
00600                 if (result != 0) reg_bl = (Bit8u)result;
00601                 else reg_cx = free_handles;
00602                 reg_ax = (result==0);
00603                 } break;
00604         default:
00605                 LOG(LOG_MISC,LOG_ERROR)("XMS: unknown function %02X",reg_ah);
00606                 reg_ax=0;
00607                 reg_bl=XMS_FUNCTION_NOT_IMPLEMENTED;
00608         }
00609 //      LOG(LOG_MISC,LOG_ERROR)("XMS: CALL Result: %02X",reg_bl);
00610         return CBRET_NONE;
00611 }
00612 
00613 bool xms_init = false;
00614 
00615 bool keep_umb_on_boot;
00616 
00617 extern Bitu VGA_BIOS_SEG;
00618 extern Bitu VGA_BIOS_SEG_END;
00619 extern Bitu VGA_BIOS_Size;
00620 
00621 bool XMS_IS_ACTIVE() {
00622         return (xms_callback != 0);
00623 }
00624 
00625 bool XMS_HMA_EXISTS() {
00626         return XMS_IS_ACTIVE() && xms_hma_exists;
00627 }
00628 
00629 Bitu GetEMSType(const Section_prop* section);
00630 void DOS_GetMemory_Choose();
00631 
00632 void ROMBIOS_FreeUnusedMinToLoc(Bitu phys);
00633 bool MEM_unmap_physmem(Bitu start,Bitu end);
00634 Bitu ROMBIOS_MinAllocatedLoc();
00635 
00636 void RemoveUMBBlock() {
00637         /* FIXME: Um... why is umb_available == false even when set to true below? */
00638         if (umb_init) {
00639                 LOG_MSG("Removing UMB block 0x%04x-0x%04x\n",first_umb_seg,first_umb_seg+first_umb_size-1);
00640                 MEM_unmap_physmem((unsigned long)first_umb_seg<<4ul,(((unsigned long)first_umb_seg+(unsigned long)first_umb_size)<<4ul)-1ul);
00641                 umb_init = false;
00642         }
00643 }
00644 
00645 class XMS: public Module_base {
00646 private:
00647         CALLBACK_HandlerObject callbackhandler;
00648 public:
00649         XMS(Section* configuration):Module_base(configuration){
00650                 Section_prop * section=static_cast<Section_prop *>(configuration);
00651                 umb_available=false;
00652 
00653         xms_global_enable = false;
00654         xms_local_enable_count = 0;
00655 
00656                 if (!section->Get_bool("xms")) return;
00657 
00658         XMS_HANDLES = (unsigned int)(section->Get_int("xms handles"));
00659         if (XMS_HANDLES == 0)
00660             XMS_HANDLES = XMS_HANDLES_DEFAULT;
00661         else if (XMS_HANDLES < XMS_HANDLES_MIN)
00662             XMS_HANDLES = XMS_HANDLES_MIN;
00663         else if (XMS_HANDLES > XMS_HANDLES_MAX)
00664             XMS_HANDLES = XMS_HANDLES_MAX;
00665 
00666         LOG_MSG("XMS: %u handles allocated for use by the DOS environment",XMS_HANDLES);
00667 
00668                 /* NTS: Disable XMS emulation if CPU type is less than a 286, because extended memory did not
00669                  *      exist until the CPU had enough address lines to read past the 1MB mark.
00670                  *
00671                  *      The other reason we do this is that there is plenty of software that assumes 286+ instructions
00672                  *      if they detect XMS services, including but not limited to:
00673                  *
00674                  *      MSD.EXE Microsoft Diagnostics
00675                  *      Microsoft Windows 3.0
00676                  *
00677                  *      Not emulating XMS for 8086/80186 emulation prevents the software from crashing. */
00678 
00679                 /* TODO: Add option to allow users to *force* XMS emulation, overriding this lockout, if they're
00680                  *       crazy enough to see what happens or they want to witness the mis-detection mentioned above. */
00681                 if (CPU_ArchitectureType < CPU_ARCHTYPE_286) {
00682                         LOG_MSG("CPU is 80186 or lower model that lacks the address lines needed for 'extended memory' to exist, disabling XMS");
00683                         return;
00684                 }
00685 
00686                 xms_init = true;
00687 
00688                 xms_hma_exists = section->Get_bool("hma");
00689                 xms_hma_minimum_alloc = (unsigned int)section->Get_int("hma minimum allocation");
00690                 xms_hma_alloc_non_dos_kernel_control = section->Get_bool("hma allow reservation");
00691                 if (xms_hma_minimum_alloc > 0xFFF0U) xms_hma_minimum_alloc = 0xFFF0U;
00692 
00693                 Bitu i;
00694                 BIOS_ZeroExtendedSize(true);
00695                 DOS_AddMultiplexHandler(multiplex_xms);
00696 
00697                 enable_a20_on_windows_init = section->Get_bool("enable a20 on windows init");
00698                 dbg_zero_on_xms_allocmem = section->Get_bool("zero memory on xms memory allocation");
00699 
00700                 if (dbg_zero_on_xms_allocmem) {
00701                         LOG_MSG("Debug option enabled: XMS memory allocation will always clear memory block before returning\n");
00702                 }
00703 
00704                 /* place hookable callback in writable memory area */
00705                 xms_callback=RealMake(DOS_GetMemory(0x1,"xms_callback")-1,0x10);
00706                 callbackhandler.Install(&XMS_Handler,CB_HOOKABLE,Real2Phys(xms_callback),"XMS Handler");
00707                 // pseudocode for CB_HOOKABLE:
00708                 //      jump near skip
00709                 //      nop,nop,nop
00710                 //      label skip:
00711                 //      callback XMS_Handler
00712                 //      retf
00713            
00714                 for (i=0;i<XMS_HANDLES;i++) {
00715                         xms_handles[i].free=true;
00716                         xms_handles[i].mem=-1;
00717                         xms_handles[i].size=0;
00718                         xms_handles[i].locked=0;
00719                 }
00720                 /* Disable the 0 handle */
00721                 xms_handles[0].free     = false;
00722 
00723                 /* Set up UMB chain */
00724                 keep_umb_on_boot=section->Get_bool("keep umb on boot");
00725                 umb_available=section->Get_bool("umb");
00726                 first_umb_seg=section->Get_hex("umb start");
00727                 first_umb_size=section->Get_hex("umb end");
00728 
00729         /* This code will mess up the MCB chain in PCjr mode if umb=true */
00730         if (umb_available && machine == MCH_PCJR) {
00731             LOG(LOG_MISC,LOG_DEBUG)("UMB emulation is incompatible with PCjr emulation, disabling UMBs");
00732             umb_available = false;
00733         }
00734 
00735                 DOS_GetMemory_Choose();
00736 
00737                 // Sanity check
00738                 if (rombios_minimum_location == 0) E_Exit("Uninitialized ROM BIOS base");
00739 
00740                 if (first_umb_seg == 0) {
00741                         first_umb_seg = DOS_PRIVATE_SEGMENT_END;
00742                         if (first_umb_seg < (Bit16u)VGA_BIOS_SEG_END)
00743                                 first_umb_seg = (Bit16u)VGA_BIOS_SEG_END;
00744                 }
00745                 if (first_umb_size == 0) first_umb_size = (Bit16u)(ROMBIOS_MinAllocatedLoc()>>4);
00746 
00747                 if (first_umb_seg < 0xC000 || first_umb_seg < DOS_PRIVATE_SEGMENT_END) {
00748                         LOG(LOG_MISC,LOG_WARN)("UMB blocks before 0xD000 conflict with VGA (0xA000-0xBFFF), VGA BIOS (0xC000-0xC7FF) and DOSBox private area (0x%04x-0x%04x)",
00749                                 DOS_PRIVATE_SEGMENT,DOS_PRIVATE_SEGMENT_END-1);
00750                         first_umb_seg = 0xC000;
00751                         if (first_umb_seg < (Bitu)DOS_PRIVATE_SEGMENT_END) first_umb_seg = (Bitu)DOS_PRIVATE_SEGMENT_END;
00752                 }
00753                 if (first_umb_seg >= (rombios_minimum_location>>4)) {
00754                         LOG(LOG_MISC,LOG_NORMAL)("UMB starting segment 0x%04x conflict with BIOS at 0x%04x. Disabling UMBs",(int)first_umb_seg,(int)(rombios_minimum_location>>4));
00755                         umb_available = false;
00756                 }
00757 
00758         if (IS_PC98_ARCH) {
00759             bool PC98_FM_SoundBios_Enabled(void);
00760 
00761             /* Do not let the private segment overlap with anything else after segment C800:0000 including the SOUND ROM at CC00:0000.
00762              * Limiting to 32KB also leaves room for UMBs if enabled between C800:0000 and the EMS page frame at (usually) D000:0000 */
00763             unsigned int limit = 0xCFFF;
00764 
00765             if (PC98_FM_SoundBios_Enabled()) {
00766                 // TODO: What about sound BIOSes larger than 16KB?
00767                 if (limit > 0xCBFF)
00768                     limit = 0xCBFF;
00769             }
00770 
00771             if (first_umb_seg > limit)
00772                 first_umb_seg = limit;
00773             if (first_umb_size > limit)
00774                 first_umb_size = limit;
00775         }
00776 
00777                 if (first_umb_size >= (rombios_minimum_location>>4)) {
00778                         /* we can ask the BIOS code to trim back the region, assuming it hasn't allocated anything there yet */
00779                         LOG(LOG_MISC,LOG_DEBUG)("UMB ending segment 0x%04x conflicts with BIOS at 0x%04x, asking BIOS to move aside",(int)first_umb_size,(int)(rombios_minimum_location>>4));
00780                         ROMBIOS_FreeUnusedMinToLoc((unsigned int)first_umb_size<<4U);
00781                 }
00782                 if (first_umb_size >= ((unsigned int)rombios_minimum_location>>4U)) {
00783                         LOG(LOG_MISC,LOG_DEBUG)("UMB ending segment 0x%04x conflicts with BIOS at 0x%04x, truncating region",(int)first_umb_size,(int)(rombios_minimum_location>>4));
00784                         first_umb_size = ((unsigned int)rombios_minimum_location>>4u)-1u;
00785                 }
00786 
00787         Bitu GetEMSPageFrameSegment(void);
00788 
00789         bool ems_available = GetEMSType(section)>0;
00790 
00791         /* 2017/12/24 I just noticed that the EMS page frame will conflict with UMB on standard configuration.
00792          * In IBM PC mode the EMS page frame is at E000:0000.
00793          * In PC-98 mode the EMS page frame is at D000:0000. */
00794         if (ems_available && first_umb_size >= GetEMSPageFrameSegment()) {
00795             assert(GetEMSPageFrameSegment() >= 0xA000);
00796             LOG(LOG_MISC,LOG_DEBUG)("UMB overlaps EMS page frame at 0x%04x, truncating region",(unsigned int)GetEMSPageFrameSegment());
00797             first_umb_size = (Bit16u)(GetEMSPageFrameSegment() - 1);
00798         }
00799         /* UMB cannot interfere with EGC 4th graphics bitplane on PC-98 */
00800         /* TODO: Allow UMB into E000:xxxx if emulating a PC-98 that lacks 16-color mode. */
00801         if (IS_PC98_ARCH && first_umb_size >= 0xE000) {
00802             LOG(LOG_MISC,LOG_DEBUG)("UMB overlaps PC-98 EGC 4th graphics bitplane, truncating region");
00803             first_umb_size = 0xDFFF;
00804         }
00805                 if (first_umb_size < first_umb_seg) {
00806                         LOG(LOG_MISC,LOG_NORMAL)("UMB end segment below UMB start. I'll just assume you mean to disable UMBs then.");
00807                         first_umb_size = first_umb_seg - 1;
00808                         umb_available = false;
00809                 }
00810                 first_umb_size = (first_umb_size + 1 - first_umb_seg);
00811                 if (umb_available) {
00812                         LOG(LOG_MISC,LOG_NORMAL)("UMB assigned region is 0x%04x-0x%04x",(int)first_umb_seg,(int)(first_umb_seg+first_umb_size-1));
00813                         if (MEM_map_RAM_physmem((unsigned int)first_umb_seg<<4u,(((unsigned int)first_umb_seg+(unsigned int)first_umb_size)<<4u)-1u)) {
00814                                 memset(GetMemBase()+((unsigned int)first_umb_seg<<4u),0x00u,(unsigned int)first_umb_size<<4u);
00815                         }
00816                         else {
00817                                 LOG(LOG_MISC,LOG_WARN)("Unable to claim UMB region (perhaps adapter ROM is in the way). Disabling UMB");
00818                                 umb_available = false;
00819                         }
00820                 }
00821 
00822                 DOS_BuildUMBChain(umb_available&&dos_umb,ems_available);
00823                 umb_init = true;
00824 
00825         /* CP/M compat will break unless a copy of the JMP instruction is mirrored in HMA */
00826         DOS_Write_HMA_CPM_jmp();
00827         }
00828 
00829         ~XMS(){
00830                 /* Remove upper memory information */
00831                 dos_infoblock.SetStartOfUMBChain(0xffff);
00832                 if (umb_available) {
00833                         if (dos_umb) dos_infoblock.SetUMBChainState(0);
00834                         umb_available=false;
00835                 }
00836 
00837                 if (!xms_init) return;
00838 
00839                 /* Undo biosclearing */
00840                 BIOS_ZeroExtendedSize(false);
00841 
00842                 /* Remove Multiplex */
00843                 DOS_DelMultiplexHandler(multiplex_xms);
00844 
00845                 /* Free used memory while skipping the 0 handle */
00846                 for (Bitu i = 1;i<XMS_HANDLES;i++) {
00847                     xms_handles[i].locked=0;
00848             XMS_FreeMemory(i);
00849         }
00850 
00851                 xms_init = false;
00852         }
00853 
00854 };
00855 
00856 static XMS* test = NULL;
00857 
00858 void XMS_DoShutDown() {
00859         if (test != NULL) {
00860                 delete test;    
00861                 test = NULL;
00862         }
00863 }
00864 
00865 void XMS_ShutDown(Section* /*sec*/) {
00866         XMS_DoShutDown();
00867 }
00868 
00869 bool XMS_Active(void) {
00870     return (test != NULL) && xms_init;
00871 }
00872 
00873 void XMS_Startup(Section *sec) {
00874     (void)sec;//UNUSED
00875         if (test == NULL) {
00876                 LOG(LOG_MISC,LOG_DEBUG)("Allocating XMS emulation");
00877                 test = new XMS(control->GetSection("dos"));
00878         }
00879 }
00880 
00881 void XMS_Init() {
00882         LOG(LOG_MISC,LOG_DEBUG)("Initializing XMS extended memory services");
00883 
00884         AddExitFunction(AddExitFunctionFuncPair(XMS_ShutDown),true);
00885         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(XMS_ShutDown));
00886         AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(XMS_ShutDown));
00887 }
00888 
00889 //save state support
00890 namespace
00891 {
00892 class SerializeXMS : public SerializeGlobalPOD
00893 {
00894 public:
00895     SerializeXMS() : SerializeGlobalPOD("XMS")
00896     {
00897         registerPOD(xms_handles);
00898     }
00899 } dummy;
00900 }