DOSBox-X
|
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 }