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