/*
 * IBM/Microsoft Confidential
 *
 * Copyright (c) IBM Corporation 1987, 1991
 * Copyright (c) Microsoft Corporation 1987, 1991
 *
 * All Rights Reserved
 */

/*
 * eisa.c - routines to get Eisa configuration information
 */

#include <os2def16.h>
#include <sysinvar.h>
#include <doshlp.h>
#include <eisa.h>
#include <loader.h>
#include <dbprt.h>

extern void dprintf(char *,...);
extern void memsetf(void far *, int, int);

#define TRUE    1
#define FALSE   0

EisaFuncInfo efi;




/*** FindFunction -- routine to find function information
 *
 * Description:
 *      This routine finds function information that matches the specified
 *      flags.  It starts, either where it left off last time, or at the
 *      beginning (slot 0, function 0)
 *
 * Entry
 *      unsigned short FindFunction(flags, buffer, first)
 *
 *          flags   -- flags to check against Eisa function and slot info
 *                          (see Eisa spec)
 *          buffer  -- far pointer to buffer to store EISA information in
 *          first       -- non-zero, start at slot0,function 0
 *                          0, continue
 *
 * Exit
 *      success     -- returns non-zero, buffer filled in
 *      failure     -- zero
 *
 * External References
 *      GetSlotInfo --  gets EISA slot information
 *      GetFuncInfo -- gets EISA function information
 *
 * Notes
 *      The buffer is always changed, reguardless of the success of the
 *      function.  When failure is returned, the info is invalid.
 *
 ********************* Pseudo Code ********************
 *
 * Procedure FindFunction
 *
 *      if first
 *          set slot to 0
 *          set function to 0
 *
 *      get slot information
 *
 *      while slot is valid
 *
 *          if slot is not empty
 *              while more functions
 *
 *                  get function info
 *                  increment function
 *                  if flags match
 *                      return success
 *
 *          increment slot number
 *
 *      set slot to 0
 *      set function to 0
 *      return no info found
 *
 *  end
 */

unsigned short FindFunction( unsigned char fbSlotFlags,
                             unsigned char fbFuncFlags,
                             EisaFuncInfo *pefi,
                             unsigned fFirst )
{
    static unsigned char bSlot;
    static unsigned char bFunc;
    EisaSlotInfo         esi;
    unsigned char        b;

    if (fFirst)
    {
        bSlot = 0;
        bFunc = 0;
    }

    GetSlotInfo( bSlot, &esi );

    while (esi.bReturn != EISA_INVALID_SLOT)
    {

        /*
         *  insure that the slot is not empty, and all of the flags are set.
         *  the flags are tested by performing the following logic:
         *
         *    -- (requestslotflags XOR (slotflags AND requestslotflags)) --
         *
         *  if all the requested flags are set, the result will be zero
         */

        if (    (esi.bReturn != EISA_EMPTY_SLOT)
             && (!(fbSlotFlags ^ (esi.fbFunc & fbSlotFlags))) )

            while (esi.cbFunc > bFunc)
            {

                GetFunctionInfo( bSlot, bFunc, pefi );
                bFunc++;

                // if function call succeeded
                if (!pefi->bReturn)
                {

                    b = pefi->fbFunction;

                    // Enable/Disable bit reversed
                    b |= (~b & EISA_FUNC_ENABLED);

                    /*
                     *  insure that all the function flags are set.
                     *  the flags are tested by performing the following logic:
                     *
                     *  -- (reqfuncflags XOR (funcflags AND reqfuncflags)) --
                     *
                     *  if all the requested flags are set, the result will
                     *  be zero
                     */

                    if ( !(fbFuncFlags ^ (b & fbFuncFlags)) )
                        return (1);
                }

            }

        bSlot++;
        bFunc = 0;
        GetSlotInfo( bSlot, &esi );

    }

    bSlot = 0;
    bFunc = 0;
    return(0);

}




/*** CountMemory - count the number of memory blocks above 16M
 *
 * Description:
 *  This routine counts the number of memory blocks above 16M that
 *  are described in the eisa configuration info.
 *
 * Entry
 *      int CountMemory(VOID)
 *
 * Exit
 *      returns number of blocks of memory above 16M
 *
 * Notes
 *      The number of blocks may not accurately describe the
 *      number of non-contiguous block of memory existing.  The
 *      inaccuracy will be cause by having two blocks of contiguous
 *      memory described by more than one memory function.  In
 *      this case, the value returned will be greater than the
 *      actual count of memory blocks (eg. 2 instead of 1 will be
 *      returned when a block of memory from 1-20 mb is described by 2
 *      memory functions that are from 1-16 mb and 16-20 bm).
 *
 *
 * Internal References
 *  FindFunction
 *
 ********************* Pseudo Code ********************
 *
 * Procedure CountMemory
 *
 *  Find memory function
 *      while more memory information
 *          if physical address > 16M
 *              increment block count
 *
 *      return block count
 */

unsigned short CountMemory( void )
{
    unsigned long   bs;
    unsigned long   sz;
    EisaMemoryInfo *emi;
    unsigned short  usReturn;
    unsigned short  usBlockCount = 0;

    // get the function info for the first memory function
    usReturn = FindFunction( EISA_HAS_MEMORY,
                             EISA_FUNC_ENABLED | EISA_HAS_MEMORY,
                             &efi,
                             TRUE );

    while (usReturn)
    {

        emi = &efi.emi[0];
        do
        {
            // physical address is stored divided by 100h
            bs = ( ( (unsigned long)emi->bPaddrMSB << 16) +
                                                      emi->usPaddrLSW ) * 0x100;

            // size to determine if any part of the block
            // is above 16M - size is stored divided by 400h or
            // 0 specifying 64M
            sz = ( emi->usMemorySize
                           ? ( (unsigned long)emi->usMemorySize ) * 0x400
                           : _64MEGB );

            DBPRT(17,("eisa base %lx sz %lx\n",bs,sz));

            // Sanity check
            if (    (bs >= _1MEGB)                // start must be >= 1 meg
                 && (bs <= (1024L*_1MEGB) )       // start must be <= 1 gig
                 && (sz >= (2*_1MEGB) )           // must be 2 meg on card
                 && (sz <= (512L*_1MEGB) )        // max 512 meg on card
                 && ( (bs+sz) <= (1536L*_1MEGB) ) // all fit below 1.5 gig
                 && (!(emi->fb & EISA_MEMORY_TYPE_MASK)) )  // system memory
            {
                // looks ok - accept the block
                usBlockCount++;
            }

        }
        // while there are more memory entries for this function
        while ( emi++->fb & EISA_MORE_ENTRIES );

        usReturn = FindFunction( EISA_HAS_MEMORY,
                                 EISA_FUNC_ENABLED | EISA_HAS_MEMORY,
                                 &efi,
                                 FALSE );

    }

    return(usBlockCount);
}



/*** MakeEisaArena - put information for Eisa Memory not already known
 *                   about in the arena info array.
 *
 * Description
 *      This function gets the information Eisa memory functions above that
 *      found by int 15 func 88 and creates entries in the arena array for
 *      them.
 *
 * Entry
 *      unsigned short MakeEisaArena(asArena, usSize)
 *
 *          asArena -- points to arena info array to put Eisa into
 *          usSize  -- number of entries available
 *
 * Exit
 *      number of entries put into arena array
 *
 * Internal references
 *  FindFunction
 *      SortArena
 *      InsertArena
 *      DeleteArena
 *
 ********************* Pseudo Code ********************
 *
 * Procedure MakeEisaArena
 *
 *      Find a memory function
 *      while more memory functions and more arena entries
 *          if physical address > max phys address already in info array
 *              put physical address in arena entry
 *              put size in arena entry
 *              increment arena pointer
 *
 *      sort the arena array
 *
 *      for each entry
 *          if this entry and the next describe physically adjacent blocks
 *              add next size to this entry
 *              delete next entry
 *          else
 *              insert invalid range entry
 *
 *      remove size information
 *
 * end MakeEisaArena
 */

unsigned short MakeEisaArena( struct arena_s  *asArena,
                              unsigned short  usSize )
{
    EisaMemoryInfo  *emi;
    unsigned long    ulPAddr;
    unsigned long    ulPEndAddr;
    unsigned long    ulSize;
    unsigned long    KnownAddr;
    struct arena_s   as;
    unsigned short   us;
    unsigned short   usReturn;
    unsigned short   usUsed;

    us = usUsed = usReturn = 0;

    // The top of known-about memory as already filled in. Ie., as found
    // by int 15 func 88.
    KnownAddr = (asArena-1)->a_paddr + (asArena-1)->a_size;
    if(KnownAddr < _16MEGB)
        KnownAddr = _16MEGB;

    DBPRT(17,("EKAddr=%lx\n",KnownAddr));

    memsetf( &as, 0, sizeof(struct arena_s) );

    usReturn = FindFunction( EISA_HAS_MEMORY,
                             EISA_FUNC_ENABLED | EISA_HAS_MEMORY,
                             &efi,
                             TRUE );

    // while there are more memory functions, and more arena entries
    while ( usReturn && (usUsed < usSize) )
    {

        emi = &efi.emi[0];
        do
        {
            // Physical address is stored divided by 100h
            ulPAddr = 0x100 * (((unsigned long)emi->bPaddrMSB << 16) +
                                                            emi->usPaddrLSW);

            // Size is stored divided by 400h with 0 representing 64M
            if (emi->usMemorySize)
                ulSize = ((unsigned long)emi->usMemorySize) * 0x400;
            else
                ulSize = (_64MEGB);

           DBPRT(17,("emiPA %lx Sz %lx\n",ulPAddr,ulSize));

            // End address of block found
            ulPEndAddr = ulPAddr + ulSize;

            // If any of the memory in the block is above the top of memory
            // we already know about, then we need to add it.
            if (    ( ulPEndAddr > KnownAddr )
                 && ( !(emi->fb & EISA_MEMORY_TYPE_MASK) ) )
            {

                 // If the block's starting address is below the line we
                 // already know about, then split the block.
                if ( ulPAddr < KnownAddr )
                {
                    asArena[usUsed].a_paddr = KnownAddr;
                    asArena[usUsed].a_size = ulPEndAddr - KnownAddr;
                }

                // else we need to add all of the block
                else
                {
                    asArena[usUsed].a_paddr = ulPAddr;
                    asArena[usUsed].a_size = ulSize;
                }

                // Mark it as free memory
                asArena[usUsed].a_aflags = AF_FREE;

                DBPRT(17,("add PA %lx Sz %lx\n",
                           asArena[usUsed].a_paddr,
                           asArena[usUsed].a_size));

                usUsed++;

            }
        }
        while (emi++->fb & EISA_MORE_ENTRIES);

        usReturn = FindFunction( EISA_HAS_MEMORY,
                                 EISA_FUNC_ENABLED | EISA_HAS_MEMORY,
                                 &efi,
                                 FALSE );
    }

    DBPRT(17,("ESort\n"));
    SortArena( asArena, usUsed );

    DBPRT(17,("EMrg\n"));
    // Merge adjacent blocks of memory or insert an invalid entry between
    // non-adjacent blocks
    for (us = 1; us <= usUsed; us++)
    {

        // if the memory blocks are adjacent, merge them
        if ( (asArena[us-1].a_paddr + asArena[us-1].a_size)
                                                         == asArena[us].a_paddr)
        {
            asArena[us-1].a_size += asArena[us].a_size;
            as = asArena[us];
            DeleteArena(asArena, usUsed--, &as);
        }
        // otherwise insert an invalid entry
        else
        {
            as.a_paddr = asArena[us-1].a_paddr + asArena[us-1].a_size;
            as.a_aflags = AF_INVALID;
            as.a_size = 0;
            InsertArena( asArena, usUsed++, &as );
            us++;
        }
    }

    DBPRT(17,("EX\n"));

    return(usUsed);
}




/*** SortArena - sort arena entries by physical address
 *
 * Description:
 *      This function sorts arena entries in the range defined by the pointer
 *      and the number of entries, in order of ascending physical address
 *
 * Entry
 *      void SortArena(asArena, usSize)
 *
 *          asArena -- points to entries to sort
 *          usSize  -- number of entries to sort
 *
 * Exit
 *      none
 *
 * Notes
 *      This is currently implemented as an insertion sort, since that is simple
 *  and small, and is executed once at boot time.
 *
 */
void SortArena( struct arena_s *asArena,
                unsigned short  usSize )
{
    struct arena_s as;
    short          us1, us2, us3;

    // for each entry to be sorted
    for ( us1 = 1; us1 < (int)usSize; us1++ )
    {

        // find its place in the sorted list of preceding elements
        us2 = us1 - 1;
        while (    (us2 >= 0)
                && (asArena[us1].a_paddr < asArena[us2].a_paddr) )
        {
            us2--;
        }
        us2++;

        // save the entry to be inserted
        as = asArena[us1];

        // move the entries up one
        for (us3 = us1 ; us3 > us2; us3--)
            asArena[us3] = asArena[us3 - 1];

        // insert the entry
        asArena[us2] = as;

    }

}



/*** InsertArena - inserts an arena entry by physical address
 *
 * Description
 *      This routine inserts an entry in the correct place in a sorted list
 *
 * Entry
 *      void InsertArena(asArena, usSize, Arena)
 *
 *          asArena     -- points to arena info array
 *          usSize      -- number of entries in array
 *          ArenaEntry  -- points to arena entry to insert
 *
 * Exit
 *      success         -- non zero, entry inserted
 *      failure         -- zero - indicates not enough room in array
 *
 ********************** Pseudo Code *********************
 *
 * Procedure InsertArena
 *
 *      find the index for the entry to be inserted
 *  for each entry with an index high than the insert index
 *          move the entry up one
 *      put the entry to be inserted in
 *
 * end InsertArena
 */

void InsertArena( struct arena_s *asArena,
                  unsigned short usSize,
                  struct arena_s *pas )
{
    short  us1, us2;

    // find the spot for the entry to be inserted
    us1 = usSize - 1;
    while (    (us1 >= 0)
            && (asArena[us1].a_paddr > pas->a_paddr) )
    {
        us1--;
    }
    us1++;

    // move all the items above the one to be inserted up one
    for (us2 = usSize; us2 > us1; us2--)
        asArena[us2] = asArena[us2 - 1];

    // insert the new element
    asArena[us1] = *pas;
}



/*** DeleteArena - remove an entry from the arena
 *
 * Description
 *      This routine deletes the specified entry from the array
 *
 * Entry
 *      void DeleteArena(asArena, usSize, Arena)
 *
 *          asArena     -- points to arena info array
 *          usSize      -- number of entries in array
 *          ArenaEntry  -- points to arena entry to insert
 *
 * Exit
 *      none
 *
 * Notes
 *      Since most of the code in loader.c assumes that the arena entries start
 *      zero filled, this routine zeros the entry it frees
 */

void DeleteArena( struct arena_s *asArena,
                  unsigned short  usSize,
                  struct arena_s *pas )
{
    unsigned short us1 = 0;

    // find the element
    while (asArena[us1].a_paddr != pas->a_paddr)
        us1++;

    // move all the elements above it down one
    while (us1 < (usSize-1))
    {
        asArena[us1] = asArena[us1+1];
        us1++;
    }

    // clear the last entry to be moved
    asArena[us1].a_paddr = 0;
    asArena[us1].a_size = 0;
    asArena[us1].a_aflags = 0;
}

