mirror of
https://github.com/joncampbell123/dosbox-x.git
synced 2025-10-14 10:48:18 +08:00
736 lines
24 KiB
C++
736 lines
24 KiB
C++
/*
|
|
* Copyright (C) 2002-2021 The DOSBox Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
#include "dosbox.h"
|
|
#include "logging.h"
|
|
#include "mem.h"
|
|
#include "bios.h"
|
|
#include "dos_inc.h"
|
|
|
|
// uncomment for alloc/free debug messages
|
|
#define DEBUG_ALLOC
|
|
|
|
uint16_t CONV_MAX_SEG = 0xA000;
|
|
Bitu UMB_START_SEG = 0x9FFF;
|
|
/* FIXME: This should be a variable that reflects the last RAM segment.
|
|
* That means 0x9FFF if 640KB or more, or a lesser value if less than 640KB */
|
|
//#define UMB_START_SEG 0x9fff
|
|
|
|
uint16_t first_umb_seg = 0xd000;
|
|
uint16_t first_umb_size = 0x2000;
|
|
|
|
static uint16_t memAllocStrategy = 0x00;
|
|
|
|
static void DOS_Mem_MCBdump(void) {
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
DOS_MCB mcb(mcb_segment);
|
|
DOS_MCB mcb_next(0);
|
|
Bitu counter=0;
|
|
char name[10];
|
|
char c;
|
|
|
|
LOG_MSG("DOS MCB dump:\n");
|
|
while ((c=(char)mcb.GetType()) != 'Z') {
|
|
if (counter++ > 10000) break;
|
|
if (c != 'M') break;
|
|
|
|
mcb.GetFileName(name);
|
|
LOG_MSG(" Type=0x%02x(%c) Seg=0x%04x size=0x%04x name='%s'\n",
|
|
mcb.GetType(),c,
|
|
mcb_segment+1,mcb.GetSize(),name);
|
|
mcb_next.SetPt((uint16_t)(mcb_segment+mcb.GetSize()+1));
|
|
mcb_segment+=mcb.GetSize()+1;
|
|
mcb.SetPt(mcb_segment);
|
|
}
|
|
|
|
mcb.GetFileName(name);
|
|
c = (char)mcb.GetType(); if (c < 32) c = '.';
|
|
LOG_MSG("FINAL: Type=0x%02x(%c) Seg=0x%04x size=0x%04x name='%s'\n",
|
|
mcb.GetType(),c,mcb_segment+1,mcb.GetSize(),name);
|
|
LOG_MSG("End dump\n");
|
|
}
|
|
|
|
static void DOS_Mem_E_Exit(const char *msg) {
|
|
DOS_Mem_MCBdump();
|
|
|
|
#if C_DEBUG
|
|
LOG_MSG("DOS fatal memory error: %s",msg);
|
|
throw int(7); // DOS non-fatal error (restart when debugger runs again)
|
|
#else
|
|
E_Exit("%s",msg);
|
|
#endif
|
|
}
|
|
|
|
void DOS_CompressMemory(uint16_t first_segment=0/*default*/,uint32_t healfrom=0xFFFFu) {
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
DOS_MCB mcb(mcb_segment);
|
|
DOS_MCB mcb_next(0);
|
|
Bitu counter=0;
|
|
|
|
while (mcb.GetType()!='Z') {
|
|
if(counter++ > 10000000) DOS_Mem_E_Exit("DOS_CompressMemory: DOS MCB list corrupted.");
|
|
const uint16_t nseg = (uint16_t)(mcb_segment+mcb.GetSize()+1);
|
|
mcb_next.SetPt(nseg);
|
|
if (GCC_UNLIKELY((mcb_next.GetType()!=0x4d) && (mcb_next.GetType()!=0x5a))) {
|
|
/* there are some programs that chain load other programs, but when they shrink their MCB down
|
|
* to their EXE resident size they put the stack or other data in the way of the next MCB free
|
|
* block header. Real MS-DOS appears in this case to just scan up to the last valid block and
|
|
* then right after it, write a new free MCB block there. We can't do this for the split memory
|
|
* layout of the PCjr emulation. */
|
|
if (nseg >= healfrom && (nseg+1u) < CONV_MAX_SEG && !(machine==MCH_PCJR)) {
|
|
LOG(LOG_DOSMISC,LOG_ERROR)("Corrupted MCB chain, but within the possible memory region of the DOS application.");
|
|
DOS_Mem_MCBdump();
|
|
LOG(LOG_DOSMISC,LOG_ERROR)("Declaring all memory past it as a free block. This is apparently MS-DOS behavior.");
|
|
mcb_next.SetSize(CONV_MAX_SEG - (nseg + 1u));
|
|
mcb_next.SetPSPSeg(MCB_FREE);
|
|
mcb_next.SetType('Z');
|
|
}
|
|
else {
|
|
DOS_Mem_E_Exit("Corrupt MCB chain");
|
|
}
|
|
}
|
|
if (mcb_segment >= first_segment && (mcb.GetPSPSeg()==MCB_FREE) && (mcb_next.GetPSPSeg()==MCB_FREE)) {
|
|
mcb.SetSize(mcb.GetSize()+mcb_next.GetSize()+1);
|
|
mcb.SetType(mcb_next.GetType());
|
|
} else {
|
|
mcb_segment+=mcb.GetSize()+1;
|
|
mcb.SetPt(mcb_segment);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DOS_FreeProcessMemory(uint16_t pspseg) {
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
DOS_MCB mcb(mcb_segment);
|
|
Bitu counter = 0;
|
|
|
|
for (;;) {
|
|
if(counter++ > 10000000) DOS_Mem_E_Exit("DOS_FreeProcessMemory: DOS MCB list corrupted.");
|
|
if (mcb.GetPSPSeg()==pspseg) {
|
|
mcb.SetPSPSeg(MCB_FREE);
|
|
}
|
|
if (mcb.GetType()==0x5a) break;
|
|
if (GCC_UNLIKELY(mcb.GetType()!=0x4d)) DOS_Mem_E_Exit("Corrupt MCB chain");
|
|
mcb_segment+=mcb.GetSize()+1;
|
|
mcb.SetPt(mcb_segment);
|
|
}
|
|
|
|
uint16_t umb_start=dos_infoblock.GetStartOfUMBChain();
|
|
if (umb_start==UMB_START_SEG) {
|
|
DOS_MCB umb_mcb(umb_start);
|
|
for (;;) {
|
|
if (umb_mcb.GetPSPSeg()==pspseg) {
|
|
umb_mcb.SetPSPSeg(MCB_FREE);
|
|
}
|
|
if (umb_mcb.GetType()!=0x4d) break;
|
|
umb_start+=umb_mcb.GetSize()+1;
|
|
umb_mcb.SetPt(umb_start);
|
|
}
|
|
} else if (umb_start!=0xffff) LOG(LOG_DOSMISC,LOG_ERROR)("Corrupt UMB chain: %x",umb_start);
|
|
|
|
DOS_CompressMemory();
|
|
}
|
|
|
|
uint16_t DOS_GetMemAllocStrategy() {
|
|
return memAllocStrategy;
|
|
}
|
|
|
|
bool DOS_SetMemAllocStrategy(uint16_t strat) {
|
|
if ((strat&0x3f)<3) {
|
|
memAllocStrategy = strat;
|
|
return true;
|
|
}
|
|
/* otherwise an invalid allocation strategy was specified */
|
|
return false;
|
|
}
|
|
|
|
extern bool dbg_zero_on_dos_allocmem;
|
|
|
|
void DOS_zeromem(uint16_t seg,uint16_t para) {
|
|
uint32_t ofs,cnt;
|
|
|
|
if (para == 0) return;
|
|
|
|
ofs = ((uint32_t)seg << 4);
|
|
cnt = ((uint32_t)para << 4);
|
|
if ((ofs+cnt) > 0x100000) E_Exit("DOS_zeromem out of range");
|
|
while (cnt != 0) {
|
|
mem_writeb(ofs++,0);
|
|
cnt--;
|
|
}
|
|
}
|
|
|
|
static uint16_t GetMaximumMCBFreeSize(uint16_t mcb_segment)
|
|
{
|
|
uint16_t largestSize = 0;
|
|
DOS_MCB mcb(mcb_segment);
|
|
uint16_t last_mcb_segment;
|
|
for (bool endOfChain = false; !endOfChain; mcb.SetPt(mcb_segment))
|
|
{
|
|
auto size = mcb.GetSize();
|
|
if (mcb.GetPSPSeg()==MCB_FREE) largestSize = (std::max)(largestSize, size);
|
|
endOfChain = DOS_MCB::MCBType(mcb.GetType())==DOS_MCB::MCBType::LastBlock;
|
|
last_mcb_segment = mcb_segment;
|
|
mcb_segment += size+1;
|
|
if (mcb_segment == last_mcb_segment) // Check for infinite loop
|
|
break;
|
|
}
|
|
return largestSize;
|
|
}
|
|
|
|
uint16_t DOS_GetMaximumFreeSize(uint16_t minBlocks)
|
|
{
|
|
if (memAllocStrategy & 0xc0)
|
|
{
|
|
auto umbFreeSize = GetMaximumMCBFreeSize(dos_infoblock.GetStartOfUMBChain());
|
|
if (memAllocStrategy & 0x40 || umbFreeSize >= minBlocks) return umbFreeSize;
|
|
}
|
|
return GetMaximumMCBFreeSize(dos.firstMCB);
|
|
}
|
|
|
|
bool DOS_AllocateMemory(uint16_t * segment,uint16_t * blocks) {
|
|
DOS_MCB psp_mcb(dos.psp()-1);
|
|
|
|
DOS_CompressMemory(
|
|
0/*default scan*/,
|
|
dos.psp()+psp_mcb.GetSize()/*auto-heal broken MCB chain if it occurs anywhere past the PSP memory block*/);
|
|
|
|
uint16_t bigsize=0;
|
|
uint16_t mem_strat=memAllocStrategy;
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
|
|
uint16_t umb_start=dos_infoblock.GetStartOfUMBChain();
|
|
if (umb_start==UMB_START_SEG) {
|
|
/* start with UMBs if requested (bits 7 or 6 set) */
|
|
if (mem_strat&0xc0) mcb_segment=umb_start;
|
|
} else if (umb_start!=0xffff) LOG(LOG_DOSMISC,LOG_ERROR)("Corrupt UMB chain: %x",umb_start);
|
|
|
|
DOS_MCB mcb(0);
|
|
DOS_MCB mcb_next(0);
|
|
char psp_name[9];
|
|
psp_mcb.GetFileName(psp_name);
|
|
if (umb_start==UMB_START_SEG && (dos.loaded_codepage == 936 || dos.loaded_codepage == 950 || dos.loaded_codepage == 951)) {
|
|
static constexpr std::array<const char*, 4> blacklisted {"KNL", "LIMD", "PY", "RDFNT"};
|
|
for (auto prog : blacklisted) if (!strcmp(psp_name, prog)) return false;
|
|
}
|
|
uint16_t found_seg=0,found_seg_size=0;
|
|
for (;;) {
|
|
mcb.SetPt(mcb_segment);
|
|
if (mcb.GetPSPSeg()==MCB_FREE) {
|
|
/* Check for enough free memory in current block */
|
|
uint16_t block_size=mcb.GetSize();
|
|
if (block_size<(*blocks)) {
|
|
if (bigsize<block_size) {
|
|
/* current block is largest block that was found,
|
|
but still not as big as requested */
|
|
bigsize=block_size;
|
|
}
|
|
} else if ((block_size==*blocks) && ((mem_strat & 0x3f)<2)) {
|
|
/* MCB fits precisely, use it if search strategy is firstfit or bestfit */
|
|
mcb.SetPSPSeg(dos.psp());
|
|
*segment=mcb_segment+1;
|
|
|
|
if (dbg_zero_on_dos_allocmem) DOS_zeromem(*segment,*blocks);
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_AllocateMemory(blocks=0x%04x) = 0x%04x-0x%04x",*blocks,*segment,*segment+*blocks-1);
|
|
#endif
|
|
return true;
|
|
} else {
|
|
switch (mem_strat & 0x3f) {
|
|
case 0: /* firstfit */
|
|
mcb_next.SetPt((uint16_t)(mcb_segment+*blocks+1));
|
|
mcb_next.SetPSPSeg(MCB_FREE);
|
|
mcb_next.SetType(mcb.GetType());
|
|
mcb_next.SetSize(block_size-*blocks-1);
|
|
mcb.SetSize(*blocks);
|
|
mcb.SetType(0x4d);
|
|
mcb.SetPSPSeg(dos.psp());
|
|
mcb.SetFileName(psp_name);
|
|
//TODO Filename
|
|
*segment=mcb_segment+1;
|
|
|
|
if (dbg_zero_on_dos_allocmem) DOS_zeromem(*segment,*blocks);
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_AllocateMemory(blocks=0x%04x) = 0x%04x-0x%04x",*blocks,*segment,*segment+*blocks-1);
|
|
#endif
|
|
return true;
|
|
case 1: /* bestfit */
|
|
if ((found_seg_size==0) || (block_size<found_seg_size)) {
|
|
/* first fitting MCB, or smaller than the last that was found */
|
|
found_seg=mcb_segment;
|
|
found_seg_size=block_size;
|
|
}
|
|
break;
|
|
default: /* everything else is handled as lastfit by dos */
|
|
/* MCB is large enough, note it down */
|
|
found_seg=mcb_segment;
|
|
found_seg_size=block_size;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Onward to the next MCB if there is one */
|
|
if (mcb.GetType()==0x5a) {
|
|
if ((mem_strat&0x80) && (umb_start==UMB_START_SEG)) {
|
|
/* bit 7 set: try high memory first, then low */
|
|
mcb_segment=dos.firstMCB;
|
|
mem_strat&=(~0xc0);
|
|
} else {
|
|
/* finished searching all requested MCB chains */
|
|
if (found_seg) {
|
|
/* a matching MCB was found (cannot occur for firstfit) */
|
|
if ((mem_strat & 0x3f)==0x01) {
|
|
/* bestfit, allocate block at the beginning of the MCB */
|
|
mcb.SetPt(found_seg);
|
|
|
|
mcb_next.SetPt((uint16_t)(found_seg+*blocks+1));
|
|
mcb_next.SetPSPSeg(MCB_FREE);
|
|
mcb_next.SetType(mcb.GetType());
|
|
mcb_next.SetSize(found_seg_size-*blocks-1);
|
|
|
|
mcb.SetSize(*blocks);
|
|
mcb.SetType(0x4d);
|
|
mcb.SetPSPSeg(dos.psp());
|
|
mcb.SetFileName(psp_name);
|
|
//TODO Filename
|
|
*segment=found_seg+1;
|
|
} else {
|
|
/* lastfit, allocate block at the end of the MCB */
|
|
mcb.SetPt(found_seg);
|
|
if (found_seg_size==*blocks) {
|
|
/* use the whole block */
|
|
mcb.SetPSPSeg(dos.psp());
|
|
//Not consistent with line 124. But how many application will use this information ?
|
|
mcb.SetFileName(psp_name);
|
|
*segment = found_seg+1;
|
|
|
|
if (dbg_zero_on_dos_allocmem) DOS_zeromem(*segment,*blocks);
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_AllocateMemory(blocks=0x%04x) = 0x%04x-0x%04x",*blocks,*segment,*segment+*blocks-1);
|
|
#endif
|
|
return true;
|
|
}
|
|
*segment = found_seg+1+found_seg_size - *blocks;
|
|
mcb_next.SetPt((uint16_t)(*segment-1));
|
|
mcb_next.SetSize(*blocks);
|
|
mcb_next.SetType(mcb.GetType());
|
|
mcb_next.SetPSPSeg(dos.psp());
|
|
mcb_next.SetFileName(psp_name);
|
|
// Old Block
|
|
mcb.SetSize(found_seg_size-*blocks-1);
|
|
mcb.SetPSPSeg(MCB_FREE);
|
|
mcb.SetType(0x4D);
|
|
}
|
|
|
|
if (dbg_zero_on_dos_allocmem) DOS_zeromem(*segment,*blocks);
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_AllocateMemory(blocks=0x%04x) = 0x%04x-0x%04x",*blocks,*segment,*segment+*blocks-1);
|
|
#endif
|
|
return true;
|
|
}
|
|
/* no fitting MCB found, return size of largest block */
|
|
*blocks=bigsize;
|
|
DOS_SetError(DOSERR_INSUFFICIENT_MEMORY);
|
|
return false;
|
|
}
|
|
} else mcb_segment+=mcb.GetSize()+1;
|
|
}
|
|
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_AllocateMemory(blocks=0x%04x) = 0x%04x-0x%04x",*blocks,*segment,*segment+*blocks-1);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
extern bool freed_mcb_allocate_on_resize;
|
|
|
|
bool DOS_ResizeMemory(uint16_t segment,uint16_t * blocks) {
|
|
if (segment < DOS_MEM_START+1) {
|
|
LOG(LOG_DOSMISC,LOG_ERROR)("Program resizes %X, take care",segment);
|
|
}
|
|
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_ResizeMemory(seg=0x%04x) blocks=0x%04x",segment,*blocks);
|
|
#endif
|
|
|
|
DOS_MCB mcb(segment-1);
|
|
if ((mcb.GetType()!=0x4d) && (mcb.GetType()!=0x5a)) {
|
|
DOS_SetError(DOSERR_MCB_DESTROYED);
|
|
return false;
|
|
}
|
|
|
|
if (mcb.GetPSPSeg()==MCB_FREE && !freed_mcb_allocate_on_resize) {
|
|
*blocks=0;
|
|
DOS_SetError(DOSERR_MCB_DESTROYED);
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS application attempted to resize freed memory block at segment 0x%x",(unsigned int)segment);
|
|
return false;
|
|
}
|
|
|
|
uint16_t total=mcb.GetSize();
|
|
DOS_MCB mcb_next(segment+total);
|
|
|
|
if (*blocks > total)
|
|
DOS_CompressMemory(segment-1);
|
|
else
|
|
DOS_CompressMemory();
|
|
|
|
if (*blocks<=total) {
|
|
if (GCC_UNLIKELY(*blocks==total)) {
|
|
/* Nothing to do, however if the block is freed, canonical MS-DOS behavior is to assign your PSP segment as if allocated (Incentiv by DID, party '94) */
|
|
if (mcb.GetPSPSeg()==MCB_FREE && freed_mcb_allocate_on_resize)
|
|
mcb.SetPSPSeg(dos.psp());
|
|
|
|
return true;
|
|
}
|
|
/* Shrinking MCB */
|
|
DOS_MCB mcb_new_next(segment+(*blocks));
|
|
mcb.SetSize(*blocks);
|
|
mcb_new_next.SetType(mcb.GetType());
|
|
if (mcb.GetType()==0x5a) {
|
|
/* Further blocks follow */
|
|
mcb.SetType(0x4d);
|
|
}
|
|
|
|
mcb_new_next.SetSize(total-*blocks-1);
|
|
mcb_new_next.SetPSPSeg(MCB_FREE);
|
|
mcb.SetPSPSeg(dos.psp());
|
|
DOS_CompressMemory();
|
|
return true;
|
|
}
|
|
/* MCB will grow, try to join with following MCB */
|
|
if (mcb.GetType()!=0x5a) {
|
|
if (mcb_next.GetPSPSeg()==MCB_FREE) {
|
|
total+=mcb_next.GetSize()+1;
|
|
}
|
|
}
|
|
if (*blocks<total) {
|
|
if (mcb.GetType()!=0x5a) {
|
|
/* save type of following MCB */
|
|
mcb.SetType(mcb_next.GetType());
|
|
}
|
|
mcb.SetSize(*blocks);
|
|
mcb_next.SetPt((uint16_t)(segment+*blocks));
|
|
mcb_next.SetSize(total-*blocks-1);
|
|
mcb_next.SetType(mcb.GetType());
|
|
mcb_next.SetPSPSeg(MCB_FREE);
|
|
mcb.SetType(0x4d);
|
|
mcb.SetPSPSeg(dos.psp());
|
|
return true;
|
|
}
|
|
|
|
/* at this point: *blocks==total (fits) or *blocks>total,
|
|
in the second case resize block to maximum */
|
|
|
|
if ((mcb_next.GetPSPSeg()==MCB_FREE) && (mcb.GetType()!=0x5a)) {
|
|
/* adjust type of joined MCB */
|
|
mcb.SetType(mcb_next.GetType());
|
|
}
|
|
|
|
mcb.SetSize(total);
|
|
mcb.SetPSPSeg(dos.psp());
|
|
if (*blocks==total) {
|
|
/* block fit exactly */
|
|
mcb.SetPSPSeg(dos.psp());
|
|
return true;
|
|
}
|
|
|
|
*blocks=total; /* return maximum */
|
|
DOS_SetError(DOSERR_INSUFFICIENT_MEMORY);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool DOS_FreeMemory(uint16_t segment) {
|
|
//TODO Check if allowed to free this segment
|
|
if (segment < DOS_MEM_START+1) {
|
|
LOG(LOG_DOSMISC,LOG_ERROR)("Program tried to free %X ---ERROR",segment);
|
|
DOS_SetError(DOSERR_MB_ADDRESS_INVALID);
|
|
return false;
|
|
}
|
|
|
|
DOS_MCB mcb(segment-1);
|
|
if ((mcb.GetType()!=0x4d) && (mcb.GetType()!=0x5a)) {
|
|
DOS_SetError(DOSERR_MB_ADDRESS_INVALID);
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG_ALLOC
|
|
LOG(LOG_DOSMISC,LOG_DEBUG)("DOS_FreeMemory(seg=0x%04x)",segment);
|
|
#endif
|
|
|
|
mcb.SetPSPSeg(MCB_FREE);
|
|
// DOS_CompressMemory();
|
|
return true;
|
|
}
|
|
|
|
Bitu GetEMSPageFrameSegment(void);
|
|
|
|
void DOS_BuildUMBChain(bool umb_active,bool /*ems_active*/) {
|
|
unsigned int seg_limit = (unsigned int)(MEM_ConventionalPages()*256);
|
|
|
|
/* UMBs are only possible if the machine has 1MB+64KB of RAM */
|
|
if (umb_active && (machine!=MCH_TANDY) && seg_limit >= (0x10000+0x1000-1) && first_umb_seg < GetEMSPageFrameSegment()) {
|
|
/* XMS emulation sets UMB size now.
|
|
* PCjr mode disables UMB emulation */
|
|
#if 0
|
|
if (ems_active) {
|
|
/* we can use UMBs up to the EMS page frame */
|
|
/* FIXME: when we make the EMS page frame configurable this will need to be updated */
|
|
first_umb_size = GetEMSPageFrameSegment() - first_umb_seg;
|
|
}
|
|
else if (machine == MCH_PCJR) {
|
|
/* we can use UMBs up to where PCjr wants cartridge ROM */
|
|
first_umb_size = 0xE000 - first_umb_seg;
|
|
}
|
|
#endif
|
|
|
|
dos_infoblock.SetStartOfUMBChain((uint16_t)UMB_START_SEG);
|
|
dos_infoblock.SetUMBChainState(0); // UMBs not linked yet
|
|
|
|
DOS_MCB umb_mcb(first_umb_seg);
|
|
umb_mcb.SetPSPSeg(0); // currently free
|
|
umb_mcb.SetSize(first_umb_size-1);
|
|
umb_mcb.SetType(0x5a);
|
|
|
|
/* Scan MCB-chain for last block */
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
DOS_MCB mcb(mcb_segment);
|
|
while (mcb.GetType()!=0x5a) {
|
|
mcb_segment+=mcb.GetSize()+1;
|
|
mcb.SetPt(mcb_segment);
|
|
}
|
|
|
|
/* A system MCB has to cover the space between the
|
|
regular MCB-chain and the UMBs */
|
|
uint16_t cover_mcb=(uint16_t)(mcb_segment+mcb.GetSize()+1);
|
|
mcb.SetPt(cover_mcb);
|
|
mcb.SetType(0x4d);
|
|
mcb.SetPSPSeg(0x0008);
|
|
mcb.SetSize(first_umb_seg-cover_mcb-1);
|
|
mcb.SetFileName("SC ");
|
|
|
|
} else {
|
|
dos_infoblock.SetStartOfUMBChain(0xffff);
|
|
dos_infoblock.SetUMBChainState(0);
|
|
}
|
|
}
|
|
|
|
bool DOS_LinkUMBsToMemChain(uint16_t linkstate) {
|
|
/* Get start of UMB-chain */
|
|
uint16_t umb_start=dos_infoblock.GetStartOfUMBChain();
|
|
if (umb_start!=UMB_START_SEG) {
|
|
if (umb_start!=0xffff) LOG(LOG_DOSMISC,LOG_ERROR)("Corrupt UMB chain: %x",umb_start);
|
|
return false;
|
|
} else if (dos.loaded_codepage == 936 || dos.loaded_codepage == 950 || dos.loaded_codepage == 951) {
|
|
static constexpr std::array<const char*, 6> blacklisted {"KNL", "LIMD", "PY", "RDFNT", "HAN16E", "HAN16V"};
|
|
char psp_name[9];
|
|
DOS_MCB psp_mcb(dos.psp()-1);
|
|
psp_mcb.GetFileName(psp_name);
|
|
for (auto prog : blacklisted) if (!strcmp(psp_name, prog)) return false;
|
|
}
|
|
|
|
if ((linkstate&1)==(dos_infoblock.GetUMBChainState()&1)) return true;
|
|
|
|
/* Scan MCB-chain for last block before UMB-chain */
|
|
uint16_t mcb_segment=dos.firstMCB;
|
|
uint16_t prev_mcb_segment=dos.firstMCB;
|
|
DOS_MCB mcb(mcb_segment);
|
|
while ((mcb_segment!=umb_start) && (mcb.GetType()!=0x5a)) {
|
|
prev_mcb_segment=mcb_segment;
|
|
mcb_segment+=mcb.GetSize()+1;
|
|
mcb.SetPt(mcb_segment);
|
|
}
|
|
DOS_MCB prev_mcb(prev_mcb_segment);
|
|
|
|
switch (linkstate) {
|
|
case 0x0000: // unlink
|
|
if ((prev_mcb.GetType()==0x4d) && (mcb_segment==umb_start)) {
|
|
prev_mcb.SetType(0x5a);
|
|
}
|
|
dos_infoblock.SetUMBChainState(0);
|
|
break;
|
|
case 0x0001: // link
|
|
if (mcb.GetType()==0x5a) {
|
|
if ((mcb_segment+mcb.GetSize()+1) != umb_start) {
|
|
LOG_MSG("MCB chain no longer goes to end of memory (corruption?), not linking in UMB!");
|
|
return false;
|
|
}
|
|
mcb.SetType(0x4d);
|
|
dos_infoblock.SetUMBChainState(1);
|
|
}
|
|
break;
|
|
default:
|
|
/* NTS: Some programs apparently call this function incorrectly.
|
|
* The CauseWay extender in Open Watcom 1.9's installer for example
|
|
* calls this function with AX=0x5803 BX=0x58 */
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
extern uint16_t DOS_IHSEG;
|
|
|
|
extern bool enable_dummy_device_mcb;
|
|
extern bool dos_clear_tf_on_int01;
|
|
|
|
void DOS_SetupMemory(void) {
|
|
unsigned int max_conv;
|
|
unsigned int seg_limit;
|
|
|
|
max_conv = (unsigned int)mem_readw(BIOS_MEMORY_SIZE) << (10u - 4u);
|
|
seg_limit = (unsigned int)(MEM_ConventionalPages()*256);
|
|
if (seg_limit > max_conv) seg_limit = max_conv;
|
|
UMB_START_SEG = max_conv - 1;
|
|
|
|
/* Let dos claim a few bios interrupts. Makes DOSBox more compatible with
|
|
* buggy games, which compare against the interrupt table. (probably a
|
|
* broken linked list implementation) */
|
|
uint16_t ihseg;
|
|
uint16_t ihofs;
|
|
|
|
assert(DOS_IHSEG != 0);
|
|
ihseg = DOS_IHSEG;
|
|
ihofs = 0x0E; /* DOS_IHSEG is one paragraph (16 bytes) and code below may write with negative offsets below */
|
|
|
|
real_writeb(ihseg,ihofs,(uint8_t)0xCF); //An IRET Instruction
|
|
if (machine != MCH_PCJR) RealSetVec(0x02,RealMake(ihseg,ihofs)); //BioMenace (segment<0x8000). Else, taken by BIOS NMI interrupt
|
|
RealSetVec(0x03,RealMake(ihseg,ihofs)); //Alien Incident (offset!=0)
|
|
RealSetVec(0x04,RealMake(ihseg,ihofs)); //Shadow President (lower byte of segment!=0)
|
|
RealSetVec(0x0f,RealMake(ihseg,ihofs)); //Always a tricky one (soundblaster irq)
|
|
|
|
// If INT 01h is executed, take the additional steps to clear the TF flag before return.
|
|
// Needed for 1996 demo "Dyslexia" by Threesome, which, for some reason when Sound Blaster
|
|
// is involved, likes to set the TF (trap flag) during the routine to switch back into
|
|
// protected mode. It will crash right after the LIDT instruction because of the change
|
|
// in interrupt table location.
|
|
//
|
|
// Demo ref: [http://files.scene.org/get:nl-http/mirrors/hornet/demos/1996/d/dyslexia.zip]
|
|
//
|
|
// FIXME: What do real Intel processors do in real mode if a nonzero base address is given in
|
|
// LIDT? Does it change the memory address of the real mode interrupt vector table?
|
|
if (dos_clear_tf_on_int01) {
|
|
real_writeb(ihseg,ihofs-9,0x55); // 55 PUSH BP
|
|
real_writew(ihseg,ihofs-8,0xE589); // 89 E5 MOV BP,SP
|
|
real_writew(ihseg,ihofs-6,0x6681); // 81 66 06 FEFF AND WORD PTR [BP+6],FEFF clear the TF bit
|
|
real_writeb(ihseg,ihofs-4,0x06); // ..
|
|
real_writew(ihseg,ihofs-3,0xFEFF); // ..
|
|
real_writeb(ihseg,ihofs-1,0x5D); // 5D POP BP
|
|
RealSetVec(0x01,RealMake(ihseg,ihofs-9)); //BioMenace (offset!=4)
|
|
}
|
|
else {
|
|
RealSetVec(0x01,RealMake(ihseg,ihofs)); //BioMenace (offset!=4)
|
|
}
|
|
|
|
uint16_t mcb_sizes=0;
|
|
|
|
if (enable_dummy_device_mcb) {
|
|
// Create a dummy device MCB with PSPSeg=0x0008
|
|
LOG_MSG("Dummy device MCB at segment 0x%x",DOS_MEM_START+mcb_sizes);
|
|
DOS_MCB mcb_devicedummy(DOS_MEM_START+mcb_sizes);
|
|
mcb_devicedummy.SetPSPSeg(MCB_DOS); // Devices
|
|
mcb_devicedummy.SetSize(16);
|
|
mcb_devicedummy.SetType(0x4d); // More blocks will follow
|
|
mcb_sizes+=1+16;
|
|
|
|
// We DO need to mark this area as 'SD' but leaving it blank so far
|
|
// confuses MEM.EXE (shows ???????) which suggests other software
|
|
// might have a problem with it as well.
|
|
// mcb_devicedummy.SetFileName("SD ");
|
|
}
|
|
|
|
DOS_MCB mcb(DOS_MEM_START+mcb_sizes);
|
|
mcb.SetPSPSeg(MCB_FREE); //Free
|
|
mcb.SetType(0x5a); //Last Block
|
|
if (machine==MCH_TANDY) {
|
|
/* map memory as normal, the BIOS initialization is the code responsible
|
|
* for subtracting 32KB from top of system memory for video memory. */
|
|
mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes);
|
|
CONV_MAX_SEG = seg_limit;
|
|
} else if (machine==MCH_PCJR) {
|
|
/* If there is more than 128KB of RAM, then the MCB chain must be constructed
|
|
* to exclude video memory. In that case, the BIOS values in the BDA will report
|
|
* actual memory size. I think this is what those DOS drivers for PCjr do that
|
|
* enable the use of additional RAM provided by a sidecar.
|
|
*
|
|
* If there is 128KB or less, then construct a normal MCB. The BIOS will subtract
|
|
* 16KB from top of memory. */
|
|
if (seg_limit > ((128*1024)/16)) {
|
|
DOS_MCB mcb_devicedummy((uint16_t)0x2000);
|
|
|
|
/* memory from 128k to 640k is available */
|
|
mcb_devicedummy.SetPt((uint16_t)0x2000);
|
|
mcb_devicedummy.SetPSPSeg(MCB_FREE);
|
|
mcb_devicedummy.SetSize(/*0x9FFF*/(seg_limit-1) - 0x2000);
|
|
mcb_devicedummy.SetType(0x5a);
|
|
CONV_MAX_SEG = seg_limit;
|
|
|
|
/* exclude PCJr graphics region */
|
|
mcb_devicedummy.SetPt((uint16_t)0x17ff);
|
|
mcb_devicedummy.SetPSPSeg(MCB_DOS);
|
|
mcb_devicedummy.SetSize(0x800);
|
|
mcb_devicedummy.SetType(0x4d);
|
|
|
|
/* memory below 96k */
|
|
mcb.SetSize(0x1800 - DOS_MEM_START - (2+mcb_sizes));
|
|
mcb.SetType(0x4d);
|
|
}
|
|
else {
|
|
/* Normal MCB chain, nothing special */
|
|
mcb.SetSize(/*normally 0x97FF*/(seg_limit-1) - DOS_MEM_START - mcb_sizes);
|
|
CONV_MAX_SEG = seg_limit;
|
|
}
|
|
} else {
|
|
#ifndef DEBUG_ALLOC
|
|
/* NTS: Testing suggests we can push as low as 4KB. However, Wikipedia and
|
|
* other sites suggest that the IBM PC only went as low as 16KB when
|
|
* it first sold, and even that wasn't too typical. But what the hell,
|
|
* we'll allow as little as 4KB if not for fun DOS hacking. */
|
|
if (seg_limit < ((4*1024)/16))
|
|
E_Exit("Standard PC requires at least 4K");
|
|
#endif
|
|
|
|
/* complete memory up to 640k available */
|
|
/* last paragraph used to add UMB chain to low-memory MCB chain */
|
|
mcb.SetSize(/*0x9FFE*/(seg_limit-2) - DOS_MEM_START - mcb_sizes);
|
|
CONV_MAX_SEG = seg_limit-1;
|
|
}
|
|
|
|
dos.firstMCB=DOS_MEM_START;
|
|
dos_infoblock.SetFirstMCB(DOS_MEM_START);
|
|
}
|
|
|
|
// save state support
|
|
void POD_Save_DOS_Memory( std::ostream& stream )
|
|
{
|
|
// - pure data
|
|
WRITE_POD( &memAllocStrategy, memAllocStrategy );
|
|
}
|
|
|
|
void POD_Load_DOS_Memory( std::istream& stream )
|
|
{
|
|
// - pure data
|
|
READ_POD( &memAllocStrategy, memAllocStrategy );
|
|
}
|