/*                                 MEMORY.C                                */

/* Memory management presents a challenge if one desires to be compatible
   with OS/2.  The problem centers on the fact that memory references
   (e.g., a memory address) differ between the real (8086) and protected
   (80286) modes of operation.  In the real mode, a memory reference is
   is via a "segment/offset" while in the protected mode, a memory reference
   is via a "descriptor/offset".  Both are 32-bit items with the 16-bit
   offset working the same in both.  However, segments and descriptors are
   not necessarily the same.  If compatibility with OS/2 and DOS is an
   objective, then one must not use a descriptor as if it were a segment.
   It is however safe to use a segment as if it were a descriptor.  In
   effect, this means you cannot treat the segment as if it is related to
   a memory address by a factor of 16.  A segment must be treated as if
   were a descriptor.  That is, a segment is treated as a reference (not
   necessarily an address) to the beginning of an area of memory which can
   be up to 64K.  Each byte within the area of memory is accessed via the
   offset mechanism. */

/* The data structure for memory is handled as follows:  At the beginning
   of execution, all available far memory is allocated in 64K chunks, with
   a "memory descriptor" for each chunk.  The memory desciptors for these
   chunks are linked together into memory pools.  Initially, all descriptors
   are linked to the free pool.  As a chunk is needed, it is allocated to
   a pool.  Some pools are needed only for a brief time.  In these cases,
   when a chunk is no longer needed it is returned to the free pool. */


/*+-------------------------------------------------------------------------+
  |                                                                         |
  |                         add_chunk_to_pool                               |
  |                                                                         |
  +-------------------------------------------------------------------------+*/

memory_descriptor_ptr add_chunk_to_pool(pool_descriptor_ptr pool,
                                        bit_32 size)
BeginDeclarations
#define Pool                           (*pool)
memory_descriptor_ptr                  desc;
#define Desc                           (*desc)
memory_descriptor_ptr                  prior;
#define Prior                          (*prior)
EndDeclarations
BeginCode
 prior = Null;
 desc  = free_pool.memory_descriptor_list;
 While desc IsNotNull
  BeginWhile
   If Desc.available NotLessThan size
    Then
     If prior IsNull
      Then
       free_pool.memory_descriptor_list  = Desc.next;
      Else
       Prior.next                        = Desc.next;
      EndIf;
     free_pool.pool_size              -= Desc.available;
     Pool.pool_size                   += Desc.available;
     Desc.next                         = Pool.memory_descriptor_list;
     Pool.memory_descriptor_list       = desc;
     far_set(Desc.chunk, 0, Bit_16(Desc.available));
     return(desc);
    EndIf;
   prior = desc;
   Next(desc);
  EndWhile;
 linker_error (8,"No more free far memory for pool \"%s\"\n."
                 "\tTry running again with smaller buffersize and/or\n"
                 "\tvirtualized fixup processing.\n",
                 Pool.pool_id);
 return(desc); /* To kill warning */
EndCode
#undef Pool
#undef Desc
#undef Prior

/*+-------------------------------------------------------------------------+
  |                                                                         |
  |                            allocate_memory                              |
  |                                                                         |
  +-------------------------------------------------------------------------+*/

byte_ptr allocate_memory (pool_descriptor_ptr pool, bit_32 size)
BeginDeclarations
#define Pool                           (*pool)
#define Desc                           (*desc)
#define Prev                           (*prev)
memory_descriptor_ptr                  desc;
memory_descriptor_ptr                  prev;
byte_ptr                               element;
EndDeclarations
BeginCode

/* Traverse the memory descriptor list and allocate the space from the
   first memory descriptor which has enough available space for the entire
   element to be allocated. */

 prev = NULL;
 desc = Pool.memory_descriptor_list;
 While desc IsNotNull
  BeginWhile
   If size NotGreaterThan Desc.available
    Then /* We can take the element from this chunk */
     Pool.used_bytes         += size;
     element                  = Desc.unused_base;
     Desc.unused_base        += Bit_16(size);
     Desc.available          -= size;
     If prev IsNotNull
      Then
       Prev.next                   = Desc.next;
       Desc.next                   = Pool.memory_descriptor_list;
       Pool.memory_descriptor_list = desc;
      EndIf;
     return(element);
    EndIf;
   prev = desc;
   Next(desc);
  EndWhile;

/* Well, none of the memory descriptors had enough space to allocate this
   element.  So, we will have to get another chunk, add it to this pool and
   allocate the space from the new chunk. */

 desc                         = add_chunk_to_pool(pool, size);
 Pool.used_bytes             += size;
 element                      = Desc.unused_base;
 Desc.unused_base            += Bit_16(size);
 Desc.available              -= size;
 return(element);
EndCode
#undef Pool
#undef Desc
#undef Prev

/*+-------------------------------------------------------------------------+
  |                                                                         |
  |                           initialize_memory                             |
  |                                                                         |
  |                             O/S dependent                               |
  |                                                                         |
  +-------------------------------------------------------------------------+*/

void initialize_memory()
BeginDeclarations
byte huge                             *far_memory;
bit_32                                 far_memory_size;
bit_32                                 size_this_chunk;
bit_16                                 rc;
memory_descriptor_ptr                  desc;
#define Desc                           (*desc)
EndDeclarations
BeginCode
/* First step for PC-DOS is to gobble all free paragraphs above the heap.
   The variable "far_memory" will point to that area. */

 inregs.h.ah = 0x48;                     /* DOS allocate memory */
 inregs.x.bx = 0xFFFF;                   /* Ask for too much memory */
 rc = intdos(Addr(inregs),Addr(outregs));/* Do the allocate expecting to fail*/
 If (outregs.x.cflag IsFalse) OrIf (rc IsNot 8)
  Then
   DOS_error("Problem allocating memory above heap.\n");
  EndIf;
 inregs.x.bx = outregs.x.bx;             /* Ask for the right amount this time*/
 DOS_int21("Problem allocating memory above heap.\n");

 far_memory      = (byte huge *) MakeFarPtr(outregs.x.ax,0);
 far_memory_size = Bit_32(inregs.x.bx) ShiftedLeft 4;
 If far_memory_size LessThan 65536L
  Then
   linker_error(8,"Too little memory above heap.\n"
                 "\tTry running again with smaller buffersize and/or\n"
                 "\tvirtualized fixup processing.\n");
  EndIf;

/* Initialize the static pool with it's first chunk. This is a little
   tricky because the initial value of several data structures are
   initialized simutaneously. */

 far_set(BytePtr(far_memory), 0, 0);  /* Clear initial chunk */
 desc                                 = (memory_descriptor_ptr) far_memory;
 static_pool.memory_descriptor_list   = desc;
 static_pool.used_bytes               = sizeof(memory_descriptor_type);
 static_pool.pool_size                =
 Desc.size                            =
 Desc.available                       = 65536L;
 Desc.chunk                           =
 Desc.unused_base                     = BytePtr(far_memory);
 Desc.next                            = NULL;
 far_memory                          += Desc.available;
 far_memory_size                     -= Desc.available;
 Desc.available                      -= sizeof(memory_descriptor_type);
 Desc.unused_base                    += sizeof(memory_descriptor_type);

/* Now set up the free pool */

 free_pool.pool_size                  =
 free_pool.used_bytes                 = 0L;
 free_pool.memory_descriptor_list     = NULL;
 While far_memory_size IsNotZero
  BeginWhile
   desc = (memory_descriptor_ptr) 
           allocate_memory(Addr(static_pool),
                           Bit_32(sizeof(memory_descriptor_type)));
   If far_memory_size Exceeds 65536L
    Then
     size_this_chunk                  = 65536L;
    Else
     size_this_chunk                  = far_memory_size;
    EndIf;
   Desc.chunk                         =
   Desc.unused_base                   = BytePtr(far_memory);
   Desc.size                          =
   Desc.available                     = size_this_chunk;
   free_pool.pool_size               += size_this_chunk;
   Desc.next                          = free_pool.memory_descriptor_list;
   free_pool.memory_descriptor_list   = desc;
   far_memory                        += size_this_chunk;
   far_memory_size                   -= size_this_chunk;
  EndWhile;
 free_pool.pool_id                   = "Free pool";
 static_pool.pool_id                 = "Static pool";
 dynamic_pool.pool_id                = "Dynamic pool";
 dynamic_pool.memory_descriptor_list = Null;
 dynamic_pool.pool_size              =
 dynamic_pool.used_bytes             = 0L;
 return;
EndCode
#undef Desc

/*+-------------------------------------------------------------------------+
  |                                                                         |
  |                              release_pool                               |
  |                                                                         |
  +-------------------------------------------------------------------------+*/

void release_pool(pool_descriptor_ptr pool)
BeginDeclarations
#define Pool                           (*pool)
memory_descriptor_ptr                  desc;
#define Desc                           (*desc)
memory_descriptor_ptr                  next;
EndDeclarations
BeginCode
 desc  = Pool.memory_descriptor_list;
 While desc IsNotNull
  BeginWhile
   Desc.available                    = Desc.size;
   Desc.unused_base                  = Desc.chunk;
   free_pool.pool_size              += Desc.available;
   next                              = Desc.next;
   Desc.next                         = free_pool.memory_descriptor_list;
   free_pool.memory_descriptor_list  = desc;
   desc = next;
  EndWhile;
 Pool.memory_descriptor_list = Null;
 Pool.pool_size              =
 Pool.used_bytes             = 0L;
 return;
EndCode
#undef Pool
#undef Desc
