/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/*static char *SCCSID = "src/vdev/vaspi/vamem.c, vaspi, r207 94/08/15";*/
/**************************************************************************
 *
 * SOURCE FILE NAME = VAMEM.C
 *
 * DESCRIPTIVE NAME = VASPI.SYS - OS/2 Virtual ASPI Device Driver
 *
 *
 *
 * VERSION = V1.0
 *
 * DATE
 *
 * DESCRIPTION : Memory and Queue management routines for VASPI.SYS
 *
 *      VASPI is organized as follows:
 *         VAINIT.C    - Init routine
 *         VAUSER.C    - UserHooks for VDM Creation, VDMTermination
 *         VADATA.C    - Data
 *         VAEVENT.C   - Entry point in VDD for PDD to call
 *         VAMEM.C     - Memory and queue management routines
 *         VAROUTER.C  - ASPI Router and DOS Link routines
 *
 *         VASPI.H     - Defines, etc
 *         VASPIX.H    - External data definitions
 *         VASPIP.H    - Function profile definitions
 *         VASPIMAC.H  - Macros
 *
 *
*/
#include <mvdm.h>
#include <scsi.h>
#include <aspi.h>

#include "vaspi.h"
#include "vaspix.h"
#include "vaspip.h"
#include "vaspimac.h"

#pragma BEGIN_SWAP_CODE

/*********************************************************
*                                                        *
*   Procedure Name : ReserveBuf                          *
*                                                        *
*   Description :                                        *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
ULONG ReserveBuf( LONG cBytes )
{
  PPGHEADER  pPGH, pPGHS;
  PBUFHEADER pBUF, pBUFPrev, pBUFS, pBUFSPrev, pBUFNew;
  LONG       Excess, PrevExcess;
  ULONG      PhysAddr;


  VDHRequestMutexSem( hMemorySem, -1L );

  /**
   ** Adjust cBytes
   **
   ** Subtract HEADER size and round to 8-Byte boundary.
   ** Enforce MIN_BUF_SIZE
   **/
  cBytes  = (cBytes + 0x7L) & ~0x7L;

  if ( cBytes < MIN_BUF_SIZE )
  {
    cBytes = MIN_BUF_SIZE;
  }

  /**
   ** For list of pages current allocated
   **
   **  Scan all free blocks for 'Best-Fit'
   **/

  pPGH  = pPGHead;
  pPGHS = 0;

  while ( pPGH )
  {
    pBUF       = pPGH->pBUFChain;
    pBUFPrev   = 0;
    PrevExcess = -1L;

    while ( pBUF )
    {
      /**
       ** pPGHS/pBUFS points at 'Best-Fit' candidate
       **
       **/
      Excess = pBUF->Size - cBytes;
      if ( Excess > 0 && Excess < PrevExcess )
      {
        pPGHS      = pPGH;
        pBUFS      = pBUF;
        pBUFSPrev  = pBUFPrev;
        PrevExcess = Excess;
      }

      pBUFPrev = pBUF;
      pBUF     = pBUF->pNext;
    }

    pPGH = pPGH->pNext;
  }

  /**
   ** If no candidate was found, allocate a new page
   **/

  if ( !pPGHS )
  {
    (PVOID)pPGH = (PVOID) VDHAllocDMABuffer( 4*1024L, 1, SSToDS(&PhysAddr));

    /**
     ** Link page into allocated page list
     **/

    if ( !pPGHead )
    {
      pPGHead = pPGH;
    }
    else
    {
      pPGTail->pNext = pPGH;
    }
    pPGTail = pPGH;


    /**
     ** Init Headers for new page
     **/
    memset( pPGH, 0, sizeof(PGHEADER));

    (PVOID) pPGH->pBUFChain = ((PBYTE)pPGH) + sizeof(PGHEADER);
            pPGH->PhysAddr  = PhysAddr;

    pBUF = pPGH->pBUFChain;

    pBUF->pNext = 0;
    pBUF->Size  = pPGH->MaxSize = MAX_BUF_SIZE;


    /**
     ** Set free buffer candidate pointers
     **/

    pPGHS     = pPGH;
    pBUFS     = pBUF;
    pBUFSPrev = 0;
    Excess    = MAX_BUF_SIZE - cBytes;
  }

  /**
   ** Check if its worth subdividing the free buffer. If
   ** not provide the whole allocation to the caller
   **/


  if ( Excess >= MIN_BUF_SIZE )
  {

    /**
     ** Update the existing header
     **
     ** This will cause allocations to start at the bottom
     ** of the Free Block and move towards lower addrezses.
     **
     **/
    pBUFS->Size -= cBytes;

    (PBYTE) pBUFNew = (PBYTE)pBUFS + pBUFS->Size;

  }
  else
  {
    /**
     ** Remove the entire selected free buffer from the chain
     **
     **/

    if ( pBUFSPrev )
    {
      pBUFSPrev->pNext = pBUFS->pNext;
    }
    else
    {
      pPGHS->pBUFChain = 0;
    }

    pBUFNew = pBUFS;
  }

  pPGH->cAllocations++;

  /**
   ** Set local vars for AllocBuf()
   **/
  pBufLin     = pBUFNew;
  pBufPhys    = pPGHS->PhysAddr + ((PBYTE)pBufLin - (PBYTE)pPGHS);
  cBytesAvail = cBytes;
  cBytesUsed  = 0;

  VDHReleaseMutexSem( hMemorySem );

  return( cBytes );
}

/*********************************************************
*                                                        *
*   Procedure Name : ReleaseBuf                          *
*                                                        *
*   Description :                                        *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID ReleaseBuf( PVOID pUserBuf, ULONG Size )
{
  PPGHEADER   pPGH, pPGHPrev;
  PBUFHEADER  pBUFRet, pBUF, pBUFPrev, pBUFScan, pBUFAdj;
  PBYTE       pPGHEnd;

  VDHRequestMutexSem( hMemorySem, -1L );

  (PVOID) pBUFRet = pUserBuf;

  /**
   ** For list of pages
   **
   **  Scan for page owning this buffer
   **/

  pPGH     = pPGHead;
  pPGHPrev = 0;

  while ( pPGH )
  {
    pPGHEnd = (PBYTE)pPGH + pPGH->MaxSize;

    /**
     ** If Page contains returned allocation
     **/
    if ( pBUFRet > (PBUFHEADER) pPGH &&  (PBYTE) pBUFRet < pPGHEnd )
    {
      /**
       ** Find nearest buf headers
       **/
      pBUF     = pPGH->pBUFChain;
      pBUFPrev = 0;

      while ( pBUF && (pBUF < pBUFRet) )
      {
        pBUFPrev   = pBUF;
        pBUF       = pBUF->pNext;
      }
      goto Found;
    }
    pPGHPrev = pPGH;
    pPGH     = pPGH->pNext;
  }

  Found:

  /**
   ** Did we really find the owning page. No???? Must be a bum buffer
   ** address!
   **/
  if ( pPGH )
  {

    /**
     ** Create a new header and add to free chain
     **/
    pBUFRet->Size = Size;
    pBUFRet->pNext = 0;

    if ( !pBUFPrev )
    {
      pPGH->pBUFChain = pBUFRet;
      pBUFPrev        = pBUFRet;
      pBUFRet->pNext   = 0;
    }
    else
    {
      pBUFRet->pNext  = pBUF;
      pBUFPrev->pNext = pBUFRet;
    }

    /**
     ** Scan chain for buffer absorption
     **
     ** Start with pBUFPrev
     **/

    pBUFScan = pBUFPrev;

    while ( pBUFScan->pNext )
    {
      (PBYTE) pBUFAdj = (PBYTE) pBUFScan + pBUFScan->Size;

      /**
       ** If the current buffer (pBUFScan) adsorbed its neighbor
       ** we continue to point to it to see if the combined buffer
       ** can adsorb more of its neighbors. Otherwise we move
       ** on to the next buffer in the free list.
       **/
      if ( pBUFAdj == pBUFScan->pNext )
      {
        pBUFScan->Size  += pBUFAdj->Size;
        pBUFScan->pNext  = pBUFAdj->pNext;
      }
      else
      {
        pBUFScan = pBUFScan->pNext;
      }
    }

    /**
     ** Check to see if we can free the underlying page
     **/
    if ( !--pPGH->cAllocations )
    {
      /**
       ** We should only have one free block on the
       ** page and it should be at maximum size
       **/
      if ( !( pPGH->pBUFChain->pNext ) &&
           ( pPGH->pBUFChain->Size == pPGH->MaxSize ) )
      {

        /**
         ** Remove the page from the Page chain
         **/

        if ( pPGHPrev )
        {
          if ( !(pPGHPrev->pNext = pPGH->pNext) )
          {
            pPGTail = pPGHPrev;
          }
        }
        else
        {
          if ( !(pPGHead = pPGH->pNext) )
          {
            pPGTail = 0;
          }
        }


        /**
         ** Return the page to the OS/2 Memory Mangler
         **/
        VDHFreeDMABuffer( pPGH );
      }
    }

  }

  VDHReleaseMutexSem( hMemorySem );
}

/*********************************************************
*                                                        *
*   Procedure Name : AllocBuf                            *
*                                                        *
*   Description : Returns small portions of the buffer   *
*                 reserved by ReserveBuf.                *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
PVOID AllocBuf( ULONG cBytes )
{
  if ( cBytes > cBytesAvail )
  {
    return( 0 );
  }

  memset( (PBYTE) pBufLin + cBytesUsed,
          (UCHAR) 0,
          (ULONG) cBytes              );

  cBytesAvail -= cBytes;
  cBytesUsed  += cBytes;

  return( (PVOID)((PBYTE) pBufLin + cBytesUsed - cBytes) );
}

/*********************************************************
*                                                        *
*   Procedure Name : GetDeviceWaitQueuePtr               *
*                                                        *
*   Description : This procedure returns a pointer to    *
*   the device wait queue for the current device.        *
*                                                        *
*   Input :                                              *
*        adapter - the SCSI adapter of the device        *
*        target - the target ID of the device            *
*        lun - the target LUN of the device              *
*                                                        *
*   Output : A pointer to the wait queue of the device.  *
*   This function should NEVER fail.                     *
*                                                        *
*********************************************************/
PWAITQ_HEAD GetDeviceWaitQueuePtr(UCHAR adapter,UCHAR target,UCHAR lun)

   {
   PWAITQ_HEAD pDeviceWaitQ = pWaitQStart;

   while (pDeviceWaitQ)
      if ((pDeviceWaitQ->adapterIndex == adapter) &&
          (pDeviceWaitQ->targetID == target) &&
          (pDeviceWaitQ->targetLUN == lun))
         break;
      else
         pDeviceWaitQ = pDeviceWaitQ->pNextQHead;

   return (pDeviceWaitQ);
   }

/*********************************************************
*                                                        *
*   Procedure Name : QueueDeviceWaitQueue                *
*                                                        *
*   Description : This procedure places the SRB at the   *
*   end of the device wait queue.                        *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID QueueDeviceWaitQueue(PWAITQ_HEAD pDeviceWaitQ,NPVSRB_LINK pSRBL)

   {
   PWAITQ_LINK pNewWaitQElement;

   VDHRequestMutexSem(vaspiWaitQueueSem, -1L);

   pNewWaitQElement = VDHAllocMem((sizeof(WAITQ_LINK)),
                                  VDHAM_SWAPPABLE);

   pNewWaitQElement->pNextQLink = pDeviceWaitQ->pLastLink;
   pNewWaitQElement->pPrevQLink = 0;
   pNewWaitQElement->pSRBL = pSRBL;

   /* If there are no elements in the queue, update both links. */
   /* Otherwise update the back link only. */
   if (!pDeviceWaitQ->pFirstLink)
      pDeviceWaitQ->pFirstLink = pDeviceWaitQ->pLastLink = pNewWaitQElement;
   else
      {
      pDeviceWaitQ->pLastLink->pPrevQLink = pNewWaitQElement;
      pDeviceWaitQ->pLastLink = pNewWaitQElement;
      }

   VDHReleaseMutexSem(vaspiWaitQueueSem);
   }

/*********************************************************
*                                                        *
*   Procedure Name : GetDeviceWaitQueueSRB               *
*                                                        *
*   Description : This procedure removes the SRB from the*
*   front of the wait queue.                             *
*                                                        *
*   Input :                                              *
*        pDeviceWaitQ - a pointer to the device queue    *
*                                                        *
*   Output : This function returns a pointer to the SRB  *
*   that was removed from the head of the queue or NULL  *
*   if the queue is empty.                               *
*                                                        *
*********************************************************/
NPVSRB_LINK GetDeviceWaitQueueSRB(PWAITQ_HEAD pDeviceWaitQ)
   {
   PWAITQ_LINK pNewWaitQElement;
   NPVSRB_LINK waitQueueSRB;

   VDHRequestMutexSem(vaspiWaitQueueSem, -1L);

   pNewWaitQElement = pDeviceWaitQ->pFirstLink;

   if (pNewWaitQElement)
      {
      waitQueueSRB = pNewWaitQElement->pSRBL;

      /* Update the head of the wait queue */
      pDeviceWaitQ->pFirstLink = pNewWaitQElement->pPrevQLink;

      /* If there are no more commands waiting, the end of */
      /* the queue must be re-initialized as well. */
      if (!(pDeviceWaitQ->pFirstLink))
         pDeviceWaitQ->pLastLink = 0;

      /* Release the link that is no longer needed */
      VDHFreeMem(pNewWaitQElement);
      }
   else
      waitQueueSRB = 0;

   VDHReleaseMutexSem(vaspiWaitQueueSem);

   return(waitQueueSRB);
   }

/*********************************************************
*                                                        *
*   Procedure Name : QueueActiveSRB                      *
*                                                        *
*   Description : This procedure places the SRB at the   *
*   head of the active queue.                            *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID QueueActiveSRB(NPVSRB_LINK pSRBL)
   {

   VDHRequestMutexSem(vaspiQueueSem, -1L);

   if (pSRBL)
      {
      /* Update the front of the active queue */
      pSRBL->pNextSRBL = npVirtSRBLinkActiveList;
      npVirtSRBLinkActiveList = pSRBL;
      }

   VDHReleaseMutexSem(vaspiQueueSem);

   }

/*********************************************************
*                                                        *
*   Procedure Name : RemoveActiveSRB                     *
*                                                        *
*   Description : This procedure removes the SRB that has*
*   just completed from the list of active commands.     *
*                                                        *
*   Input : A pointer to the VSRB_LINK                   *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID RemoveActiveSRB(NPVSRB_LINK pSRBLDel)
   {
   NPVSRB_LINK pSRBL;
   NPVSRB_LINK pSRBLPrev = 0;

   VDHRequestMutexSem(vaspiQueueSem, -1L);

   pSRBL = npVirtSRBLinkActiveList;

   /* Scan the list of active requests looking for */
   /* the one that has just finished. */

   while (pSRBL)
      {
      if (pSRBL != pSRBLDel )
         {
         pSRBLPrev = pSRBL;
         pSRBL     = pSRBL->pNextSRBL;
         }
      else
         break;
      }

   /* Was a match found? */

   if (pSRBL)
      {
      /* If the match was not the head of the active queue, cut it out. */
      /* Otherwise, chop the element off the front of the list. */

      if (pSRBLPrev)
         pSRBLPrev->pNextSRBL = pSRBL->pNextSRBL;
      else
         npVirtSRBLinkActiveList = npVirtSRBLinkActiveList->pNextSRBL;
      }

   VDHReleaseMutexSem(vaspiQueueSem);

   }

/*********************************************************
*                                                        *
*   Procedure Name : GetPhysicalSRBAddress               *
*                                                        *
*   Description : This procedure returns the physical    *
*   address of the OS/2 SRB corresponding to the DOS     *
*   SRB being aborted.                                   *
*                                                        *
*   Input : The linear address of the DOS SRB            *
*                                                        *
*   Output : The physical address of the OS/2 SRB        *
*                                                        *
*********************************************************/
ULONG GetPhysicalSRBAddress(PASPI_SRB_HEADER DosSRBAddress)

   {
   NPVSRB_LINK currentSRBL;
   ULONG physAddrSRB;

   VDHRequestMutexSem(vaspiQueueSem, -1L);

   currentSRBL = npVirtSRBLinkActiveList;

   while (currentSRBL)
      if ((currentSRBL->pDosSRB) == DosSRBAddress)
         break;
      else
         currentSRBL = currentSRBL->pNextSRBL;

   if (currentSRBL)
      physAddrSRB = ((PASPI_SRB_EXECUTE_IO)(currentSRBL->pOS2SRB))->ppSRB;

   VDHReleaseMutexSem(vaspiQueueSem);

   return(physAddrSRB);
   }

#pragma END_SWAP_CODE
