/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1992 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 source code is provided to you solely for       */
/*    the purpose of assisting you in your development of OS/2 device        */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Developer Connection Device Driver       */
/*    Source Kit for OS/2. This Copyright statement may not be removed.      */
/*                                                                           */
/*****************************************************************************/
/*static char *SCCSID = "src/vdev/vaspi/varouter.c, vaspi, r207 94/08/15";*/
/***************************************************************************
 *
 * SOURCE FILE NAME = VAROUTER.C
 *
 * DESCRIPTIVE NAME = VASPI.SYS - OS/2 Virtual ASPI Device Driver
 *
 *
 *
 * VERSION = V1.0
 *
 * DATE
 *
 * DESCRIPTION : ASPI router and DOS link 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"

#define LINKPHYSADDR(p) (ULONG)((PBYTE)pBufPhys+((PBYTE)p-(PBYTE)pBufLin))

#define LINKOFFSET(p)   (ULONG)((PBYTE)p-(PBYTE)pBufLin)

#define MODALToLIN(p) (!(flVdmStatus & VDM_STATUS_VPM_EXEC) ?  \
                         PFROMVADDR(WORDOF(p,1),WORDOF(p,0)) : \
                           VDHQueryLin((F16PVOID) p ))

#pragma BEGIN_SWAP_CODE

/***EP  VASPIRouter -  Route client service request
 *
 *      Route client requests for VASPI services to the proper
 *      handler.  This function is called by our stub
 *      (which is sitting in VDM memory).  The register frame
 *      contains the registers as when invoked by the application.
 *
 *      If we do not recognize the function call, we return an
 *      "Unimplemented" error.  Else, we dispatch to the proper service.
 *
 *      ENTRY   pcrf  - client's register frame.
 *                      Registers contain input parameters
 *                      for services.
 *
 *      EXIT    Always succeeds; returns TRUE.
 *              client's registers contain information, depending
 *                      on which service was called.
 *
 */

BOOL HOOKENTRY VASPIRouter(PVOID Unused, PCRF pcrf)

   {
   PUSHORT     pVDMStack;
   USHORT      RetCSIP[2];
   UCHAR       SRBScratchArea[4];
   PWAITQ_HEAD pDeviceWaitQ;

   /* Get Client CS:EIP following call to VDM BP */
   VDHPopStack(4L,
               (PVOID) SSToDS(RetCSIP));

   /* Set Client CS:EIP */
   CS(pcrf) = RetCSIP[1];
   IP(pcrf) = RetCSIP[0];


   /* Get SRB address from Client Stack */
   pVDMStack = (PUSHORT) VPFROMVADDR(SS(pcrf),
                                     SP(pcrf));
   pVDMStack = MODALToLIN(pVDMStack);

   V86SRBPtr = *(PULONG) pVDMStack;
   pDosSRB   = MODALToLIN(V86SRBPtr);

   /* ASPI for Windows uses Pascal calling convention. */
   /* Remove the client SRB from the stack. */
   if (flVdmStatus & VDM_STATUS_VPM_EXEC)
      VDHPopStack(4L,
                  (PVOID) SSToDS(SRBScratchArea));

   /**
    ** Note: The following instance vars are used and assumed
    **       to be initialized:
    **
    **       pDosSRB   - Client SRB Pointer
    **       SRBSize   - Client SRB Size
    **       pSRBL     - SRB Shadow structure
    **       V86SRBPtr - Original Client SRB Ptr
    **/

   /**
    ** Make sure the SRB status is clear because some applications may
    ** not update all fields when they reuse an SRB
    **/
   pDosSRB->ASPIStatus = ASPI_STATUS_IN_PROGRESS;

   switch (pDosSRB->CommandCode)
      {
      case ASPI_CMD_ADAPTER_INQUIRY:
         SRBSize = sizeof(ASPI_SRB_INQUIRY);
         DoSimpleSRB();
         break;

      case ASPI_CMD_GET_DEVICE_TYPE:
         SRBSize = sizeof(ASPI_SRB_DEVICE_TYPE);
         DoSimpleSRB();
         break;

      case ASPI_CMD_EXECUTE_IO:
         if (!DoVirtASPIExecuteIO())
            return TRUE;
         break;

      case ASPI_CMD_ABORT_IO:
         DoVirtASPIAbortIO();
         break;

      case ASPI_CMD_RESET_DEVICE:
         DoVirtASPIResetDevice();
         break;

      default:
         pDosSRB->ASPIStatus = ASPI_STATUS_INVALID_COMMAND;
         return TRUE;
      }

   /**
    ** Create 16:16 alias to shadow SRB
    **/
   ((PSEL)(&pSRBL->pSRBL16))[1] = VDHCreateSel(pSRBL,
                                               pSRBL->AllocBytes);
   OFFSETOF16(pSRBL->pSRBL16) = NULL;

   /* We need to make sure that we don't overwhelm the VDD with overlapped */
   /* requests from the post routine of the application. Since the context */
   /* hooks are guaranteed to execute before the application code, it is */
   /* quite possible that small requests can finish and force the post code */
   /* to issue another request without completing execution of the post */
   /* routine. The net result of this would be that no post completion hooks */
   /* would ever get freed and the system would eventually halt. */
   /*                                                                   */
   /* To avoid this situation, we will only issue commands to OS2ASPI when */
   /* the current device is not within the application's post routine. Once */
   /* the application returns control to ASPIPostReturn, we will issue the */
   /* command at the front of the wait queue. */

   if ((pDosSRB->CommandCode == ASPI_CMD_EXECUTE_IO) &&
       (pDosSRB->ASPIReqFlags & ASPI_REQFLAG_POST_ENABLE))
      {
      pDeviceWaitQ = GetDeviceWaitQueuePtr(pDosSRB->AdapterIndex,
                                           ((NPDASPI_SRB_EXECUTE_IO) pDosSRB)->DeviceTargetID,
                                           ((NPDASPI_SRB_EXECUTE_IO) pDosSRB)->DeviceTargetLUN);

      if (pDeviceWaitQ->PostRoutineActive == TRUE)
         {
         QueueDeviceWaitQueue(pDeviceWaitQ,
                              pSRBL);
         }
      else
         {
         QueueActiveSRB(pSRBL);

         /* Let PDD do all of the real work. */
         PDDProc(PDDCMD_SRB_REQUEST,
                 (PVOID) VPFROMVADDR(SEGMENTOF16(pSRBL->pSRBL16),
                                     pSRBL->linkOffset),
                 (PVOID) NULL);
         }
      }
   else
      {
      QueueActiveSRB(pSRBL);

      /* Let PDD do all of the real work. */
      PDDProc(PDDCMD_SRB_REQUEST,
              (PVOID) VPFROMVADDR(SEGMENTOF16(pSRBL->pSRBL16),
                                  pSRBL->linkOffset),
              (PVOID) NULL);
      }

   return TRUE;
   }

/*********************************************************
*                                                        *
*   Procedure Name : DoSimpleSRB                         *
*                                                        *
*   Description : This procedure converts a DOS ASPI     *
*   SRB into the equivalent OS/2 request.                *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID DoSimpleSRB()

   {
   cAllocBytes = ReserveBuf(SRBSize+sizeof(VSRB_LINK));
   SetupSRB();
   }


/*********************************************************
*                                                        *
*   Procedure Name : SetupSRB                            *
*                                                        *
*   Description : This procedure converts a DOS ASPI     *
*   SRB into the equivalent OS/2 request.                *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID SetupSRB()

   {
   (PVOID) pSRBL = AllocBuf(sizeof(VSRB_LINK));

   pSRBL->AllocBytes = cAllocBytes;
   pSRBL->pDosSRB    = pDosSRB;
   pSRBL->V86SRBPtr  = V86SRBPtr;
   pSRBL->hHVDM      = sessionHandle;
   pSRBL->Flags      = (flVdmStatus & VDM_STATUS_VPM_EXEC) ? LF_VPM_CLIENT : 0;

   (PVOID) pSRBL->pOS2SRB = AllocBuf(SRBSize);

   memcpy(pSRBL->pOS2SRB,
          pDosSRB,
          SRBSize);

   /* The LinkOffset calculation requires an instance variable. */
   /* It is best to do the calculation once and store the result */
   /* for later use. */
   pSRBL->linkOffset = LINKOFFSET(pSRBL->pOS2SRB);
   }


/*********************************************************
*                                                        *
*   Procedure Name : DoVirtASPIExecuteIO                 *
*                                                        *
*   Description : This procedure converts a DOS ASPI     *
*   I/O request into the equivalent OS/2 request.        *
*                                                        *
*   Input :                                              *
*        npVirtSRBLink - a pointer to the VDD structure  *
*           associated with this request                 *
*                                                        *
*        v8086Mode - a flag indicating session is        *
*           executing in v8086 mode                      *
*                                                        *
*   Output : This function returns TRUE if the entire    *
*   DOS ASPI request was converted and FALSE if an error *
*   was detected.                                        *
*                                                        *
*********************************************************/
BOOL DoVirtASPIExecuteIO()

   {
   PVDHPAGELIST pPGList;

   ULONG        cPGList;
   ULONG        SRBToProcess = TRUE;
   PVOID        pDataBuf;

   NPDASPI_SRB_EXECUTE_IO pDosSRBIO;
   NPASPI_SRB_EXECUTE_IO  pOS2SRBIO;

   ULONG        lockFlags;

   ULONG        maximumCDBTransfer;
   USHORT       maximumSGList;

   NPVSRB_LINK  pPrevSRBL = NULL;

   while (SRBToProcess)
      {

      pDosSRBIO = (NPDASPI_SRB_EXECUTE_IO) pDosSRB;
      maximumCDBTransfer = adapterInfo[pDosSRB->AdapterIndex].maximumCDBTransfer;
      maximumSGList = adapterInfo[pDosSRB->AdapterIndex].maximumSGList;

      SRBSize = sizeof(ASPI_SRB_EXECUTE_IO) - 1
                + pDosSRBIO->SCSICDBLen
                + pDosSRBIO->SenseDataLen;

      /* Special check for the programmers who insist  on sending SRBs with */
      /* uninitialized DataXferLen. This may result in a system halt so we */
      /* will change it for them! */
      ConfirmDataTransfer(pDosSRBIO);

      cAllocBytes =
           ReserveBuf(SRBSize + MAX_VIRT_SG_LIST_SIZE + sizeof(VSRB_LINK));

      SetupSRB();

      /* Allocate a hook and save it in the hook data area */
      pSRBL->hkVDMContext = VDHAllocHook((ULONG)  VDH_CONTEXT_HOOK,
                                         (PFNARM) ASPIContextRoutine,
                                         (ULONG)  sizeof(NPVSRB_LINK));

      pOS2SRBIO = (PASPI_SRB_EXECUTE_IO) pSRBL->pOS2SRB;

      /**
       ** Set the Physical Address field of the OS2 SRB. We
       ** know the link Physical Addr and the OS2SRB offset.
       **/
      pOS2SRBIO->ppSRB = LINKPHYSADDR(pSRBL->pOS2SRB);

      /**
       ** Get ready to lock the client data buffer and
       ** take back a pagelist.
       **/
      (PVOID) pPGList = AllocBuf(MAX_VIRT_SG_LIST_SIZE);

      if (pDosSRBIO->DataXferLen)
         {
         pDataBuf = MODALToLIN(pDosSRBIO->pDataBuffer);
         if (adapterInfo[pDosSRB->AdapterIndex].adapterFeatures &
             ASPI_ADDRESS_LIMITED)
            lockFlags = VDHLM_RESIDENT | VDHLM_16M;
         else
            lockFlags = VDHLM_RESIDENT;

         /* Attempt to validate the size of the data transfer before */
         /* locking the buffer in place. If the S/G list is too large */
         /* for the buffer at pPGList, it may trash another part of */
         /* the buffer pool. */
         if (pDosSRBIO->DataXferLen < MAX_VIRT_SG_LIST * 4096)
            {
            pSRBL->hDataLock = VDHLockMem((PVOID) pDataBuf,
                                          (ULONG) pDosSRBIO->DataXferLen,
                                          lockFlags,
                                          (PVDHPAGELIST) pPGList,
                                          (PULONG) SSToDS(&cPGList));
            }
         else
            {
            cPGList = MAX_VIRT_SG_LIST + 1;
            pSRBL->hDataLock = 0;
            }

         /**
          ** For a single entry PageList specify the buffer address
          ** directly in the SRB. Otherwise, set the buffer address
          ** to point to the PageList.
          **/

         if (cPGList > 1)
            {

            /* Update the OS2 SRB to reflect the new request information */

            pOS2SRBIO->SRBHdr.ASPIReqFlags |= ASPI_REQFLAG_SG_ENABLE;

            pOS2SRBIO->SGListLen = cPGList;
            pOS2SRBIO->ppDataBuffer = LINKPHYSADDR(pPGList);

            /**
             ** If the if the transfer length is too large for the adapter
             ** to handle, we need to release everything and report the
             ** error to the application. If the S/G list is larger than
             ** the MaxHWSGList for the adapter we must not issue the
             ** command either, because the ADD may not be able to break
             ** it up into smaller requests.
             **/

            if (((pDosSRBIO->DataXferLen > maximumCDBTransfer) &&
                  maximumCDBTransfer) ||
                ((cPGList > maximumSGList) &&
                  maximumSGList))
               {
               /* Since posting may be enabled we should update the error */
               /* code in the OS2 SRB and then call the event handler. */
               pOS2SRBIO->SRBHdr.ASPIStatus = ASPI_STATUS_BUFFER_TOO_BIG;

               /* Create 16:16 alias to shadow SRB */
               ((PSEL)(&pSRBL->pSRBL16))[1] = VDHCreateSel(pSRBL,
                                                           pSRBL->AllocBytes);
               OFFSETOF16(pSRBL->pSRBL16) = NULL;

               /* Add the SRB that failed to the queue. */
               QueueActiveSRB(pSRBL);

               /* Call VASPIEventProc directly instead of OS2ASPI.DMD */
               VASPIEventProc(0,
                              (PVOID) VPFROMVADDR(SEGMENTOF16(pSRBL->pSRBL16),
                                                  pSRBL->linkOffset),
                              0);
               return FALSE;
               }

            }
         else
            pOS2SRBIO->ppDataBuffer = pPGList->vdhpl_paddr;
         }

      /* If a previous SRBL exists, link it to the current SRBL */
      if (pPrevSRBL )
         pPrevSRBL->pNextSRBL = pSRBL;

      /* If there is a linked SRB, setup an OS2 SRB for it as well */

      SRBToProcess = FALSE;
      if (pDosSRBIO->SRBHdr.ASPIReqFlags & ASPI_REQFLAG_LINKED_SRB)
         {
         (PVOID) pDosSRB = MODALToLIN( pDosSRBIO->pNxtSRB );
         SRBToProcess = TRUE;
         pPrevSRBL = pSRBL;
         }
      }

   return (TRUE);
   }


/*********************************************************
*                                                        *
*   Procedure Name : ConfirmDataTransfer                 *
*                                                        *
*   Description : This procedure verifies that the SRB   *
*   is issuing a command that requires a data transfer   *
*   length.                                              *
*                                                        *
*   Input :                                              *
*        pDOSSRBIO - A pointer to the DOS SRB            *
*                                                        *
*   Output : This procedure will reset the data transfer *
*   length in the DOS SRB to 0 if it determines that no  *
*   data transfer will be performed.                     *
*                                                        *
*********************************************************/
VOID ConfirmDataTransfer(NPDASPI_SRB_EXECUTE_IO pDosSRBIO)

   {
   PWAITQ_HEAD pDeviceWaitQ;

   if (pDosSRBIO->DataXferLen)
      {
      switch (pDosSRBIO->SCSICDBStart[0])
         {
      case SCSI_LOAD_UNLOAD:
         // @175152 Start.
         // Command code 0x1B can be LOAD/UNLOAD for SCSI_SEQACCESS (tape devices),
         //                          SCAN for SCSI_SCANNER
         pDeviceWaitQ = GetDeviceWaitQueuePtr(pDosSRBIO->SRBHdr.AdapterIndex,
                                              pDosSRBIO->DeviceTargetID,
                                              pDosSRBIO->DeviceTargetLUN);
         if (pDeviceWaitQ->deviceType == SCSI_SCANNER)
         {
            break;  // Do not touch DataXferLen which is length of        list, normally 1.
         }
         // @175152 end.
         case SCSI_TEST_UNIT_READY:
         case SCSI_REWIND:
         case SCSI_SPACE:
         case SCSI_RESERVE_UNIT:
         case SCSI_RELEASE_UNIT:
         case SCSI_ERASE:
         // @175152  case SCSI_LOAD_UNLOAD:
         case SCSI_LOCK_UNLOCK:
            pDosSRBIO->DataXferLen = 0;
            break;
         }

      /* If the length is too big for DOS, reset it to an acceptable value. */
      /* Allow a Win-OS/2 VDM to use larger buffers because certain apps    */
      /* (I.E. scanners) have large requirements. */
      if (!(flVdmStatus & VDM_STATUS_VPM_APP) &&
           (pDosSRBIO->DataXferLen > (512 * 1024)))
         pDosSRBIO->DataXferLen = 512 * 1024;
      }
   }

/*********************************************************
*                                                        *
*   Procedure Name : DoVirtASPIAbortIO                   *
*                                                        *
*   Description : This procedure converts a DOS ASPI     *
*   abort request into the equivalent OS/2 request.      *
*                                                        *
*   Input :                                              *
*        npVirtSRBLink - a pointer to the VDD structure  *
*           associated with this request                 *
*                                                        *
*        v8086Mode - a flag indicating session is        *
*           executing in v8086 mode                      *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID DoVirtASPIAbortIO()

   {
   NPASPI_SRB_ABORT_IO pSRBAB;

   SRBSize = sizeof(ASPI_SRB_ABORT_IO);

   cAllocBytes = ReserveBuf(SRBSize+sizeof(VSRB_LINK));

   SetupSRB();

   pSRBAB = (NPASPI_SRB_ABORT_IO) pDosSRB;

   /**
    ** For asynchronous requests, allocate a Context Hook to
    ** get us back to the VDM context when the request
    ** completes.
    **/
   pSRBL->hkVDMContext = VDHAllocHook((ULONG)  VDH_CONTEXT_HOOK,
                                      (PFNARM) ASPIContextRoutine,
                                      (ULONG)  sizeof(NPVSRB_LINK));

   /* Retrieve the physical address of the pending request */
   pSRBAB->ppSRB = GetPhysicalSRBAddress(MODALToLIN(pSRBAB->ppSRB));
   }

/*********************************************************
*                                                        *
*   Procedure Name : DoVirtASPIResetDevice               *
*                                                        *
*   Description : This procedure converts a DOS ASPI     *
*   reset request into the equivalent OS/2 request.      *
*                                                        *
*   Input :                                              *
*        npVirtSRBLink - a pointer to the VDD structure  *
*           associated with this request                 *
*                                                        *
*        v8086Mode - a flag indicating session is        *
*           executing in v8086 mode                      *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID DoVirtASPIResetDevice()

   {

   SRBSize = sizeof(ASPI_SRB_RESET_DEVICE);

   cAllocBytes = ReserveBuf(SRBSize+sizeof(VSRB_LINK));

   SetupSRB();

   /**
    ** For asynchronous requests, allocate a Context Hook to
    ** get us back to the VDM context when the request
    ** completes.
    **/
   pSRBL->hkVDMContext = VDHAllocHook((ULONG)  VDH_CONTEXT_HOOK,
                                      (PFNARM) ASPIContextRoutine,
                                      (ULONG)  sizeof(NPVSRB_LINK));
   }
