/* Catacomb Armageddon Source Code * Copyright (C) 1993-2014 Flat Rock Software * * 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. */ // NEWMM.C /* ============================================================================= ID software memory manager -------------------------- Primary coder: John Carmack RELIES ON --------- Quit (char *error) function WORK TO DO ---------- MM_SizePtr to change the size of a given pointer Multiple purge levels utilized EMS / XMS unmanaged routines ============================================================================= */ #include "ID_HEADS.H" #pragma hdrstop #pragma warn -pro #pragma warn -use #if 0 // 1 == Debug/Dev ; 0 == Production/final #define OUT_OF_MEM_MSG "MM_GetPtr: Out of memory!\nYou were short :%ld bytes" #else #define OUT_OF_MEM_MSG "\n" \ "You need more memory to run CATACOMB ARMAGEDDON. Read the INSTRUCTION\n" \ "section of the START program for tips on getting more memory.\n" #endif /* ============================================================================= LOCAL INFO ============================================================================= */ #define LOCKBIT 0x80 // if set in attributes, block cannot be moved #define PURGEBITS 3 // 0-3 level, 0= unpurgable, 3= purge first #define PURGEMASK 0xfffc #define BASEATTRIBUTES 0 // unlocked, non purgable #define MAXUMBS 10 typedef struct mmblockstruct { unsigned start,length; unsigned attributes; memptr *useptr; // pointer to the segment start struct mmblockstruct far *next; } mmblocktype; //#define GETNEWBLOCK {if(!(mmnew=mmfree))Quit("MM_GETNEWBLOCK: No free blocks!")\ // ;mmfree=mmfree->next;} #define GETNEWBLOCK {if(!mmfree)MML_ClearBlock();mmnew=mmfree;mmfree=mmfree->next;} #define FREEBLOCK(x) {*x->useptr=NULL;x->next=mmfree;mmfree=x;} /* ============================================================================= GLOBAL VARIABLES ============================================================================= */ mminfotype mminfo; memptr bufferseg; boolean mmerror; void (* beforesort) (void); void (* aftersort) (void); /* ============================================================================= LOCAL VARIABLES ============================================================================= */ boolean mmstarted; void far *farheap; void *nearheap; mmblocktype far mmblocks[MAXBLOCKS] ,far *mmhead,far *mmfree,far *mmrover,far *mmnew; boolean bombonerror; unsigned totalEMSpages,freeEMSpages,EMSpageframe,EMSpagesmapped,EMShandle; void (* XMSaddr) (void); // far pointer to XMS driver unsigned numUMBs,UMBbase[MAXUMBS]; //========================================================================== // // local prototypes // boolean MML_CheckForEMS (void); void MML_ShutdownEMS (void); void MM_MapEMS (void); boolean MML_CheckForXMS (void); void MML_ShutdownXMS (void); void MML_UseSpace (unsigned segstart, unsigned seglength); void MML_ClearBlock (void); //========================================================================== /* ====================== = = MML_CheckForEMS = = Routine from p36 of Extending DOS = ======================= */ char emmname[9] = "EMMXXXX0"; boolean MML_CheckForEMS (void) { asm mov dx,OFFSET emmname[0] asm mov ax,0x3d00 asm int 0x21 // try to open EMMXXXX0 device asm jc error asm mov bx,ax asm mov ax,0x4400 asm int 0x21 // get device info asm jc error asm and dx,0x80 asm jz error asm mov ax,0x4407 asm int 0x21 // get status asm jc error asm or al,al asm jz error asm mov ah,0x3e asm int 0x21 // close handle asm jc error // // EMS is good // return true; error: // // EMS is bad // return false; } /* ====================== = = MML_SetupEMS = ======================= */ void MML_SetupEMS (void) { char str[80],str2[10]; unsigned error; totalEMSpages = freeEMSpages = EMSpageframe = EMSpagesmapped = 0; asm { mov ah,EMS_STATUS int EMS_INT // make sure EMS hardware is present or ah,ah jnz error mov ah,EMS_VERSION int EMS_INT or ah,ah jnz error cmp al,0x32 // only work on ems 3.2 or greater jb error mov ah,EMS_GETFRAME int EMS_INT // find the page frame address or ah,ah jnz error mov [EMSpageframe],bx mov ah,EMS_GETPAGES int EMS_INT // find out how much EMS is there or ah,ah jnz error mov [totalEMSpages],dx mov [freeEMSpages],bx or bx,bx jz noEMS // no EMS at all to allocate cmp bx,4 jle getpages // there is only 1,2,3,or 4 pages mov bx,4 // we can't use more than 4 pages } getpages: asm { mov [EMSpagesmapped],bx mov ah,EMS_ALLOCPAGES // allocate up to 64k of EMS int EMS_INT or ah,ah jnz error mov [EMShandle],dx } return; error: error = _AH; strcpy (str,"MML_SetupEMS: EMS error 0x"); itoa(error,str2,16); strcpy (str,str2); Quit (str); noEMS: ; } /* ====================== = = MML_ShutdownEMS = ======================= */ void MML_ShutdownEMS (void) { if (!EMShandle) return; asm { mov ah,EMS_FREEPAGES mov dx,[EMShandle] int EMS_INT or ah,ah jz ok } Quit ("MML_ShutdownEMS: Error freeing EMS!"); ok: ; } /* ==================== = = MM_MapEMS = = Maps the 64k of EMS used by memory manager into the page frame = for general use. This only needs to be called if you are keeping = other things in EMS. = ==================== */ void MM_MapEMS (void) { char str[80],str2[10]; unsigned error; int i; for (i=0;istart+scan->length < segstart) { last = scan; scan = scan->next; } // // take the given range out of the block // oldend = scan->start + scan->length; extra = oldend - (segstart+seglength); if (extra < 0) Quit ("MML_UseSpace: Segment spans two blocks!"); if (segstart == scan->start) { last->next = scan->next; // unlink block FREEBLOCK(scan); scan = last; } else scan->length = segstart-scan->start; // shorten block if (extra > 0) { GETNEWBLOCK; mmnew->next = scan->next; scan->next = mmnew; mmnew->start = segstart+seglength; mmnew->length = extra; mmnew->attributes = LOCKBIT; } } //========================================================================== /* ==================== = = MML_ClearBlock = = We are out of blocks, so free a purgable block = ==================== */ void MML_ClearBlock (void) { mmblocktype far *scan,far *last; scan = mmhead->next; while (scan) { if (!(scan->attributes&LOCKBIT) && (scan->attributes&PURGEBITS) ) { MM_FreePtr(scan->useptr); return; } scan = scan->next; } Quit ("MM_ClearBlock: No purgable blocks!"); } //========================================================================== /* =================== = = MM_Startup = = Grabs all space from turbo with malloc/farmalloc = Allocates bufferseg misc buffer = =================== */ static char *ParmStrings[] = {"noems","noxms",""}; void MM_Startup (void) { int i; unsigned long length; void far *start; unsigned segstart,seglength,endfree; if (mmstarted) MM_Shutdown (); mmstarted = true; bombonerror = true; // // set up the linked list (everything in the free list; // mmhead = NULL; mmfree = &mmblocks[0]; for (i=0;istart = 0; mmnew->length = 0xffff; mmnew->attributes = LOCKBIT; mmnew->next = NULL; mmrover = mmhead; // // get all available near conventional memory segments // length=coreleft(); start = (void far *)(nearheap = malloc(length)); length -= 16-(FP_OFF(start)&15); length -= SAVENEARHEAP; seglength = length / 16; // now in paragraphs segstart = FP_SEG(start)+(FP_OFF(start)+15)/16; MML_UseSpace (segstart,seglength); mminfo.nearheap = length; // // get all available far conventional memory segments // length=farcoreleft(); start = farheap = farmalloc(length); length -= 16-(FP_OFF(start)&15); length -= SAVEFARHEAP; seglength = length / 16; // now in paragraphs segstart = FP_SEG(start)+(FP_OFF(start)+15)/16; MML_UseSpace (segstart,seglength); mminfo.farheap = length; mminfo.mainmem = mminfo.nearheap + mminfo.farheap; // // detect EMS and allocate up to 64K at page frame // mminfo.EMSmem = 0; for (i = 1;i < _argc;i++) { if ( US_CheckParm(_argv[i],ParmStrings) == 0) goto emsskip; // param NOEMS } if (MML_CheckForEMS()) { MML_SetupEMS(); // allocate space MML_UseSpace (EMSpageframe,EMSpagesmapped*0x400); MM_MapEMS(); // map in used pages mminfo.EMSmem = EMSpagesmapped*0x4000l; } // // detect XMS and get upper memory blocks // emsskip: mminfo.XMSmem = 0; for (i = 1;i < _argc;i++) { if ( US_CheckParm(_argv[i],ParmStrings) == 0) goto xmsskip; // param NOXMS } if (MML_CheckForXMS()) MML_SetupXMS(); // allocate as many UMBs as possible // // allocate the misc buffer // xmsskip: mmrover = mmhead; // start looking for space after low block MM_GetPtr (&bufferseg,BUFFERSIZE); } //========================================================================== /* ==================== = = MM_Shutdown = = Frees all conventional, EMS, and XMS allocated = ==================== */ void MM_Shutdown (void) { if (!mmstarted) return; farfree (farheap); free (nearheap); MML_ShutdownEMS (); MML_ShutdownXMS (); } //========================================================================== /* ==================== = = MM_GetPtr = = Allocates an unlocked, unpurgable block = ==================== */ void MM_GetPtr (memptr *baseptr,unsigned long size) { mmblocktype far *scan,far *lastscan,far *endscan ,far *purge,far *next; int search; unsigned needed,startseg; needed = (size+15)/16; // convert size from bytes to paragraphs GETNEWBLOCK; // fill in start and next after a spot is found mmnew->length = needed; mmnew->useptr = baseptr; mmnew->attributes = BASEATTRIBUTES; for (search = 0; search<3; search++) { // // first search: try to allocate right after the rover, then on up // second search: search from the head pointer up to the rover // third search: compress memory, then scan from start if (search == 1 && mmrover == mmhead) search++; switch (search) { case 0: lastscan = mmrover; scan = mmrover->next; endscan = NULL; break; case 1: lastscan = mmhead; scan = mmhead->next; endscan = mmrover; break; case 2: MM_SortMem (); lastscan = mmhead; scan = mmhead->next; endscan = NULL; break; } startseg = lastscan->start + lastscan->length; while (scan != endscan) { if (scan->start - startseg >= needed) { // // got enough space between the end of lastscan and // the start of scan, so throw out anything in the middle // and allocate the new block // purge = lastscan->next; lastscan->next = mmnew; mmnew->start = *(unsigned *)baseptr = startseg; mmnew->next = scan; while ( purge != scan) { // free the purgable block next = purge->next; FREEBLOCK(purge); purge = next; // purge another if not at scan } mmrover = mmnew; return; // good allocation! } // // if this block is purge level zero or locked, skip past it // if ( (scan->attributes & LOCKBIT) || !(scan->attributes & PURGEBITS) ) { lastscan = scan; startseg = lastscan->start + lastscan->length; } scan=scan->next; // look at next line } } if (bombonerror) Quit (OUT_OF_MEM_MSG,(size-mminfo.nearheap)); else mmerror = true; } //========================================================================== /* ==================== = = MM_FreePtr = = Allocates an unlocked, unpurgable block = ==================== */ void MM_FreePtr (memptr *baseptr) { mmblocktype far *scan,far *last; last = mmhead; scan = last->next; if (baseptr == mmrover->useptr) // removed the last allocated block mmrover = mmhead; while (scan->useptr != baseptr && scan) { last = scan; scan = scan->next; } if (!scan) Quit ("MM_FreePtr: Block not found!"); last->next = scan->next; FREEBLOCK(scan); } //========================================================================== /* ===================== = = MM_SetPurge = = Sets the purge level for a block (locked blocks cannot be made purgable) = ===================== */ void MM_SetPurge (memptr *baseptr, int purge) { mmblocktype far *start; start = mmrover; do { if (mmrover->useptr == baseptr) break; mmrover = mmrover->next; if (!mmrover) mmrover = mmhead; else if (mmrover == start) Quit ("MM_SetPurge: Block not found!"); } while (1); mmrover->attributes &= ~PURGEBITS; mmrover->attributes |= purge; } //========================================================================== /* ===================== = = MM_SetLock = = Locks / unlocks the block = ===================== */ void MM_SetLock (memptr *baseptr, boolean locked) { mmblocktype far *start; start = mmrover; do { if (mmrover->useptr == baseptr) break; mmrover = mmrover->next; if (!mmrover) mmrover = mmhead; else if (mmrover == start) Quit ("MM_SetLock: Block not found!"); } while (1); mmrover->attributes &= ~LOCKBIT; mmrover->attributes |= locked*LOCKBIT; } //========================================================================== /* ===================== = = MM_SortMem = = Throws out all purgable stuff and compresses movable blocks = ===================== */ void MM_SortMem (void) { mmblocktype far *scan,far *last,far *next; unsigned start,length,source,dest,oldborder; int playing; // // lock down a currently playing sound // playing = SD_SoundPlaying (); if (playing) { switch (SoundMode) { case sdm_PC: playing += STARTPCSOUNDS; break; case sdm_AdLib: playing += STARTADLIBSOUNDS; break; } MM_SetLock(&(memptr)audiosegs[playing],true); } SD_StopSound(); // oldborder = bordercolor; // VW_ColorBorder (15); if (beforesort) beforesort(); scan = mmhead; last = NULL; // shut up compiler warning while (scan) { if (scan->attributes & LOCKBIT) { // // block is locked, so try to pile later blocks right after it // start = scan->start + scan->length; } else { if (scan->attributes & PURGEBITS) { // // throw out the purgable block // next = scan->next; FREEBLOCK(scan); last->next = next; scan = next; continue; } else { // // push the non purgable block on top of the last moved block // if (scan->start != start) { length = scan->length; source = scan->start; dest = start; while (length > 0xf00) { movedata(source,0,dest,0,0xf00*16); length -= 0xf00; source += 0xf00; dest += 0xf00; } movedata(source,0,dest,0,length*16); scan->start = start; *(unsigned *)scan->useptr = start; } start = scan->start + scan->length; } } last = scan; scan = scan->next; // go to next block } mmrover = mmhead; if (aftersort) aftersort(); // VW_ColorBorder (oldborder); if (playing) MM_SetLock(&(memptr)audiosegs[playing],false); } //========================================================================== #if 0 /* ===================== = = MM_ShowMemory = ===================== */ void MM_ShowMemory (void) { mmblocktype far *scan; unsigned color,temp; long end,owner; char scratch[80],str[10]; VW_SetDefaultColors(); VW_SetLineWidth(40); temp = bufferofs; bufferofs = 0; VW_SetScreen (0,0); scan = mmhead; end = -1; //CA_OpenDebug (); while (scan) { if (scan->attributes & PURGEBITS) color = 5; // dark purple = purgable else color = 9; // medium blue = non purgable if (scan->attributes & LOCKBIT) color = 12; // red = locked if (scan->start<=end) Quit ("MM_ShowMemory: Memory block order currupted!"); end = scan->start+scan->length-1; VW_Hlin(scan->start,(unsigned)end,0,color); VW_Plot(scan->start,0,15); if (scan->next->start > end+1) VW_Hlin(end+1,scan->next->start,0,0); // black = free #if 0 strcpy (scratch,"Size:"); ltoa ((long)scan->length*16,str,10); strcat (scratch,str); strcat (scratch,"\tOwner:0x"); owner = (unsigned)scan->useptr; ultoa (owner,str,16); strcat (scratch,str); strcat (scratch,"\n"); write (debughandle,scratch,strlen(scratch)); #endif scan = scan->next; } //CA_CloseDebug (); IN_Ack(); VW_SetLineWidth(64); bufferofs = temp; } #endif //========================================================================== /* ====================== = = MM_UnusedMemory = = Returns the total free space without purging = ====================== */ long MM_UnusedMemory (void) { unsigned free; mmblocktype far *scan; free = 0; scan = mmhead; while (scan->next) { free += scan->next->start - (scan->start + scan->length); scan = scan->next; } return free*16l; } //========================================================================== /* ====================== = = MM_TotalFree = = Returns the total free space with purging = ====================== */ long MM_TotalFree (void) { unsigned free; mmblocktype far *scan; free = 0; scan = mmhead; while (scan->next) { if ((scan->attributes&PURGEBITS) && !(scan->attributes&LOCKBIT)) free += scan->length; free += scan->next->start - (scan->start + scan->length); scan = scan->next; } return free*16l; } //========================================================================== /* ===================== = = MM_BombOnError = ===================== */ void MM_BombOnError (boolean bomb) { bombonerror = bomb; }