/*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/vauser.c, vaspi, r207 94/08/15";*/
/***************************************************************************
 *
 * SOURCE FILE NAME = VAUSER.C
 *
 * DESCRIPTIVE NAME = VASPI.SYS - OS/2 Virtual ASPI Device Driver
 *
 *
 *
 * VERSION = V1.0
 *
 * DATE
 *
 * DESCRIPTION : This module contains the initialization code
 *               used whenever a new VDM is created or destroyed.                                     ;
 *
 *      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 LINKOFFSET(p)   (ULONG)((PBYTE)p-(PBYTE)pBufLin)

#pragma BEGIN_SWAP_CODE

/***EP  VASPICreate - VASPI VDM creation time initialization
 *
 *      Called when a new VDM is created.  If we realize that
 *      we cannot proceed with the installation we back out of
 *      the installation  and release anything we may have reserved
 *      in the process.
 *
 *      ENTRY
 *              None   (we do not use the hvdm parameter)
 *
 *      EXIT-SUCCESS    - returns TRUE
 *      EXIT-FAILURE    - returns FALSE, and all reversible actions
 *                              are undone
 *
 *      Notes:
 *
 *      (1) Since the Control Function must find a way to get to protected
 *          mode from real mode, we ask VDHRegisterAPI to create a V86
 *          breakpoint address.  The VDM stub obtains the address via the
 *          Int 2F AX=4011 interface.  The function itself
 *          is not invoked via an INT, but is instead called by the
 *          Control Function which is sitting in the lower 1Meg.
 *
 *      (2) The Device Driver's INIT function will be called after VDM gets
 *          itself all hooked up, but before the user's application starts
 *          up.  Only at this point do we hook into interrupts
 *
 */

BOOL PASCAL VASPICreate(HVDM hvdm)
   {
   USHORT i;
   UCHAR currAdapter;
   UCHAR currTarget;
   UCHAR currLun;
   PVOID  pbDDDStart;
   PWAITQ_HEAD pWaitQElement;
   PWAITQ_HEAD pPrevElement = 0;

   sessionHandle = hvdm;


   if ( (VDHCreateSem(&vaspiQueueSem,VDH_MUTEXSEM)) &&

        (VDHCreateSem(&vaspiWaitQueueSem,VDH_MUTEXSEM)) &&

        /* Allocate a timer hook for use during VASPIDestroy */
        ((hTimerHook = VDHAllocHook(VDH_TIMER_HOOK,
                                   (PFNARM) TimerExpired,
                                    0))) )
      {

       /**
        ** RegisterAPI creates a V86 Breakpoint. The VDD
        ** can obtain the address of the breakpoint using
        ** Int 2F AX=4011.
        **/

       VDHRegisterAPI("VASPI",
                      (PFNHOOK) VASPIService,
                      (PFNHOOK) VASPIVPMService);


       /**
        ** Create an ASPI Manager DD header in the DOS Box.
        **
        ** For VMBoot sessions ASPIVDD.SYS is loaded
        ** which supplies its own header.
        **/

       if (!(*(PSZ)VDHQueryProperty(szVMBoot)))
          {
          if (pbDDDStart = VDHAllocDosMem(ASPIStubLength))
             {
              memcpy(pbDDDStart,
                     ASPIStubStart,
                     ASPIStubLength);
              VDHSetDosDevice((VPDOSDDTYPE) VPFROMP(pbDDDStart));
             }
          }


       /* Allocate a wait queue head for each target/LUN available */
       for (currAdapter = 0; currAdapter < adapterCount; currAdapter++)
          for (currTarget =0; currTarget < 16; currTarget++)
             for (currLun = 0; currLun < 8; currLun++)
                {
                GetDeviceInfo(pDeviceSRB,
                              currAdapter,
                              currTarget,
                              currLun);
                if (pDeviceSRB->SRBHdr.ASPIStatus == ASPI_STATUS_NO_ERROR)
                   {
                   /* Allocate a queue head */
                   pWaitQElement = VDHAllocMem(sizeof(WAITQ_HEAD),
                                               VDHAM_SWAPPABLE);

                   /* Initialize the queue elements */
                   if (!pWaitQStart)
                      pWaitQStart = pWaitQElement;

                   if (pPrevElement)
                      pPrevElement->pNextQHead = pWaitQElement;

                   pWaitQElement->pNextQHead = 0;
                   pWaitQElement->pNextAdapter = 0;
                   pWaitQElement->adapterIndex = currAdapter;
                   pWaitQElement->targetID = currTarget;
                   pWaitQElement->targetLUN = currLun;
                   pWaitQElement->deviceType = pDeviceSRB->DeviceType;       // @175152
                   pWaitQElement->PostRoutineActive = FALSE;
                   pWaitQElement->pFirstLink = pWaitQElement->pLastLink = 0;

                   pPrevElement = pWaitQElement;
                   }
                }

       /* Remember that the VDM can now issue SRBs */
       VDMActive = TRUE;

      }

   return TRUE;
   }

/*********************************************************
*                                                        *
*   Procedure Name : GetDeviceInfo                       *
*                                                        *
*   Description : This procedure retrieves the device    *
*   information for the specified device from OS2ASPI.   *
*                                                        *
*   Input :                                              *
*        pSRB - A pointer to the SRB being issued        *
*        adapter - The adapter number for the device     *
*        target - The device ID                          *
*        lun - The device LUN                            *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID GetDeviceInfo(PASPI_SRB_DEVICE_TYPE pSRB, UCHAR adapter,
                   UCHAR target, UCHAR lun)

   {
   PVOID SRBSelector;

   /* Create an adapter info request and issue it to OS2ASPI */
   pSRB->SRBHdr.CommandCode = ASPI_CMD_GET_DEVICE_TYPE;
   pSRB->SRBHdr.ASPIStatus = ASPI_STATUS_IN_PROGRESS;
   pSRB->SRBHdr.AdapterIndex = adapter;
   pSRB->SRBHdr.ASPIReqFlags = 0;
   pSRB->DeviceTargetID = target;
   pSRB->DeviceTargetLUN = lun;

   /* Create a 16:16 shadow pointer to the SRB */
   ((PSEL)(&SRBSelector))[1] = VDHCreateSel(pSRB,
                                            sizeof(ASPI_SRB_DEVICE_TYPE));
   OFFSETOF16(SRBSelector) = NULL;

   /* Let PDD do all of the real work. */
   PDDProc(PDDCMD_OS2_ASPI_PASSTHRU,
           (PVOID) SRBSelector,
           (PVOID) NULL);

   /* Release the 16:16 shadow pointer to the SRB */
   VDHDestroySel((SEL) SEGMENTOF16(SRBSelector));
   }

/*********************************************************
*                                                        *
*   Procedure Name : VASPIDestroy                        *
*                                                        *
*   Description : This procedure cleans up the VDM by    *
*   purging all queues and releasing all memory that may *
*   have been allocated by the VDD.                      *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID PASCAL VASPIDestroy(HVDM hvdm)
   {
   NPVSRB_LINK pSRBL;
   NPVSRB_LINK pNextSRBL;
   PVOID SRBSelector;
   HLOCK abortSRBLockHandle;
   BOOL  SRBPending;
   ULONG lockFlags;

   PWAITQ_HEAD pDeviceWaitQ;
   PWAITQ_HEAD pPrevDeviceWaitQ;
   PWAITQ_LINK pWaitSRB;
   PWAITQ_LINK pPrevWaitSRB;


   /* Remember that no more I/O should be started, the VDM is dying */
   VDMActive = FALSE;

   /* Release all the memory and resources associated with SRB that */
   /* are still in the wait queue for each device. */
   pDeviceWaitQ = pWaitQStart;

   while (pDeviceWaitQ)
      {
      pWaitSRB = pDeviceWaitQ->pFirstLink;

      while (pWaitSRB)
         {
         pSRBL = pWaitSRB->pSRBL;

         /* Release the context hook associated with the SRB */
         VDHFreeHook(pSRBL->hkVDMContext);

         /* Release the lock on the data buffer */
         if (pSRBL->hDataLock)
            VDHUnlockMem(pSRBL->hDataLock);

         /* Release the selector:offset allocated for OS2ASPI.DMD */
         VDHDestroySel((SEL)SEGMENTOF16(pSRBL->pSRBL16));

         /* Release the SRB buffer */
         ReleaseBuf(pSRBL,
                    pSRBL->AllocBytes);

         /* Get the next waiting SRB */
         pPrevWaitSRB = pWaitSRB;
         pWaitSRB = pWaitSRB->pNextQLink;

         /* Now release the resources associated with WAITQ_LINK */
         VDHFreeMem(pPrevWaitSRB);
         }

      /* Get the next device waiting queue */
      pPrevDeviceWaitQ = pDeviceWaitQ;
      pDeviceWaitQ = pDeviceWaitQ->pNextQHead;

      /* Now release the resources associated with WAITQ_HEAD */
      VDHFreeMem(pPrevDeviceWaitQ);
      }

   /* Scan the queue for active I/O requests and abort them */
   pSRBL = npVirtSRBLinkActiveList;

   /* We need to record which SRBs are still awaiting I/O completion */
   /* and issue an abort for each of them. These commands that are being */
   /* aborted may take a LONG time to finish; for example a tape drive */
   /* may be doing an erase or rewind and will not reconnect to the bus */
   /* until the command completes. */
   /*                                                                   */
   /* Since the VDM will be long gone when the disconnected command has */
   /* completed, OS2ASPI will fake us out and announce that the aborted */
   /* command was immediately aborted. This should be acceptable because */
   /* there is no longer anything alive to read the status. */

   while(pSRBL)
      {
      if ((((PASPI_SRB_HEADER) pSRBL->pOS2SRB)->CommandCode == ASPI_CMD_EXECUTE_IO) &&
          (((PASPI_SRB_HEADER) pSRBL->pOS2SRB)->ASPIStatus == ASPI_STATUS_IN_PROGRESS))
         {
         /* Remember that the active SRB belongs to a dead VDM */
         pSRBL->Flags |= LF_VDM_DESTROYED;

         /* Create an OS/2 abort request and issue it to OS2ASPI */
         pAbortSRB->SRBHdr.CommandCode = ASPI_CMD_ABORT_IO;
         pAbortSRB->SRBHdr.ASPIStatus = ASPI_STATUS_IN_PROGRESS;
         pAbortSRB->SRBHdr.AdapterIndex = pSRBL->pOS2SRB->AdapterIndex;
         pAbortSRB->SRBHdr.ASPIReqFlags = 0;
         pAbortSRB->ppSRB = ((PASPI_SRB_EXECUTE_IO) pSRBL->pOS2SRB)->ppSRB;

         if (adapterInfo[pAbortSRB->SRBHdr.AdapterIndex].adapterFeatures &
             ASPI_ADDRESS_LIMITED)
            lockFlags = VDHLM_RESIDENT | VDHLM_16M;
         else
            lockFlags = VDHLM_RESIDENT;

         /* Lock the SRB in memory */
         abortSRBLockHandle = VDHLockMem(pAbortSRB,
                                         sizeof(ASPI_SRB_ABORT_IO),
                                         lockFlags,
                                         (PVOID) VDHLM_NO_ADDR,
                                         (PVOID) NULL);

         /* Create a 16:16 shadow pointer to the SRB */
         ((PSEL)(&SRBSelector))[1] = VDHCreateSel(pAbortSRB,
                                                  sizeof(ASPI_SRB_ABORT_IO));
         OFFSETOF16(SRBSelector) = NULL;

         /* Arm the timer for the current SRB */
         timerRunning = TRUE;
         VDHArmTimerHook(hTimerHook,
                         30 * 1000,
                         0);

         /* Update the queue before issuing the abort. The queues may */
         /* get modified in the process of handling the abort. If there */
         /* is only 1 active SRB and we abort it, the SRBL memory may get */
         /* allocated by some other process. This would invalidate the */
         /* pNextSRBL, which we would assume to be NULL in that case. */
         pSRBL = pSRBL->pNextSRBL;

         /* Let PDD do all of the real work. */
         PDDProc(PDDCMD_VDM_DESTROY,
                 (PVOID) SRBSelector,
                 (PVOID) NULL);

         /* Poll for completion of the current SRB */
         while ((pAbortSRB->SRBHdr.ASPIStatus == ASPI_STATUS_IN_PROGRESS) &&
                (timerRunning))
            {
            ;
            }

         /* Disarm the timer hook for the current SRB */
         if (timerRunning)
            {
            VDHDisarmTimerHook(hTimerHook);
            timerRunning = FALSE;
            }

         /* Release the 16:16 shadow pointer to the SRB */
         VDHDestroySel((SEL) SEGMENTOF16(SRBSelector));

         /* Unlock the SRB */
         VDHUnlockMem(abortSRBLockHandle);
         }
      else
         pSRBL = pSRBL->pNextSRBL;
      }

   /* Eliminate all session semaphores */
   if ( vaspiQueueSem )
      VDHDestroySem(vaspiQueueSem);
   if ( vaspiWaitQueueSem )
      VDHDestroySem(vaspiWaitQueueSem);
   }

/*********************************************************
*                                                        *
*   Procedure Name : TimerExpired                        *
*                                                        *
*   Description : This procedure signals that the system *
*   timer used to track a request during VASPIDestroy    *
*   has expired.                                         *
*                                                        *
*   Input :                                              *
*                                                        *
*   Output :                                             *
*                                                        *
*********************************************************/
VOID HOOKENTRY TimerExpired(PVOID Unused, PCRF pCRF)

   {

   /* Signal that the timer has expired */
   timerRunning = FALSE;

   }

/*********************************************************
*                                                        *
*   Procedure Name : VASPIVPMService                     *
*                                                        *
*   Description : This routine is called by the DPMI     *
*   application in the DOS session. The call to          *
*   VDHRegisterAPI in VASPICreate routine (above) was    *
*   used to provide a mechanism for DOS applications to  *
*   communicate dirtectly with the virtual device        *
*   driver.                                              *
*                                                        *
*   Input :                                              *
*            Unused                                      *
*            pCRF - Pointer to client regsiter frame     *
*                                                        *
*   Output :                                             *
*            AX(pCRF) = SEGMENTOF32(VASPIRouter routine) *
*            DI(pCRF) = OFFSETOF16(VASPIRouter routine)  *
*                                                        *
*********************************************************/
VOID HOOKENTRY VASPIVPMService(PVOID Unused, PCRF pCRF)
   {
   USHORT     RetCSIP[2];

   VDHPopStack(4L,
               (PVOID) SSToDS(RetCSIP));

   CS(pCRF) = RetCSIP[1];
   IP(pCRF) = RetCSIP[0];

   if (!pVPMBP)
      {
       hVPMBP = VDHAllocHook(VDH_BP_HOOK,
                            (PFNARM) VASPIRouter,
                             0);
       pVPMBP=VDHArmVPMBPHook(hVPMBP);
      }

   AX(pCRF) = SEGMENTOF32(pVPMBP);
   DI(pCRF) = OFFSETOF16(pVPMBP);
   }

/*********************************************************
*                                                        *
*   Procedure Name : VASPIService                        *
*                                                        *
*   Description : This routine is called by the DOS      *
*   application in the DOS session. The call to          *
*   VDHRegisterAPI in VASPICreate routine (above) was    *
*   used to provide a mechanism for DOS applications to  *
*   communicate dirtectly with the virtual device        *
*   driver.                                              *
*                                                        *
*   Input :                                              *
*            Unused                                      *
*            pCRF - Pointer to client regsiter frame     *
*                                                        *
*   Output :                                             *
*            AX(pCRF) = SEGMENTOF16(VASPIRouter routine) *
*            DI(pCRF) = OFFSETOF16(VASPIRouter routine)  *
*                                                        *
*********************************************************/
VOID HOOKENTRY VASPIService(PVOID Unused, PCRF pCRF)
   {
   USHORT     RetCSIP[2];

   VDHPopStack(4L,
               (PVOID) SSToDS(RetCSIP));

   CS(pCRF) = RetCSIP[1];
   IP(pCRF) = RetCSIP[0];

   if (!pV86BP)
      {
       hV86BP=VDHAllocHook(VDH_BP_HOOK,
                          (PFNARM) VASPIRouter,
                           0);
       pV86BP=VDHArmBPHook(hV86BP);
      }

   AX(pCRF) = SEGMENTOF16(pV86BP);
   DI(pCRF) = OFFSETOF16(pV86BP);
   }

#pragma END_SWAP_CODE

