/*static char *SCCSID = "@(#)bvsldrvr.c	6.1 90/11/16";*/
/*static char *SCCSID = "@(#)bvsldrvr.c 6.8 90/01/28";*/
/****************************************************************************/
/*                                                                          */
/*                       IBM/Microsoft Confidential                         */
/*                                                                          */
/*                 Copyright (c) IBM Corporation  1987, 1990                */
/*                 Copyright (c) Microsoft Corp.  1987, 1990                */
/*                           All Rights Reserved                            */
/*                                                                          */
/****************************************************************************/

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <doscalls.h>
#include <dos.h>
#include <error2.h>

#include <os2def.h>
#define INCL_WINSHELLDATA
#include <pmshl.h>

#include "bvsldrvr.h"
#include "bvsdata.h"

/*----------------------------------------------------------------------*/
/* The following routines were totally rewritten:               D261    */
/*      BvsGetVideoCallTables                                   D261    */
/*----------------------------------------------------------------------*/

/***    BvsGetVideoCallTables - get call vectors for all adapters
*
*       This routine gets the video device handler strings from the system
*       configuration file.  For each of these, DosLoadVideoHandler is called
*       to get the call vector for the device handler.  The return config
*       information function will be called on each of the device handlers
*       to determine the power up configuration.  The first VDH that
*       confesses to being the power up display will be moved to the head
*       of the table of vectors.
*
*       PSEUDOCODE
*
*       ENTRY   pchTableOfCallVectors - pointer to the table of call tables
*
*       EXIT    return  - 0 if success
*                         otherwise errorcode
*/

unsigned far pascal
BvsGetVideoCallTables ( ppcalltbl, ppszProtPtrNames, ppszRealPtrNames)
CALLTBL far **ppcalltbl;
char far ** ppszProtPtrNames;           /* @P1 */
char far ** ppszRealPtrNames;           /* @P1 */
{
    unsigned    rc = 0;                 /* @@TB3 */
    unsigned    i,j;
    char far    *pszTmp;
    CALLTBL far *pcalltblTmp;

    SaveRegs();

    if ( (ppszProtPtrNames != NULL) &&
        (ppszRealPtrNames != NULL) ) {  /* Call table update required */

        if (fFillDevBlk == fcFillInitDeviceBlock) { /* Begin code for @S16 */

            i = iPowerUpVDH;            /* start with PowerUp vector */
            while (i <= cVideoDevices) {

                                        /* load the video driver */
                if (!DosLoadVideoHandler(&ppcalltbl[iPowerUpVDH],
                    pszDeviceString[i])) {

                    if (fIsPowerup(ppcalltbl[iPowerUpVDH])) {
                        pszDeviceString[iPowerUpVDH] = pszDeviceString[i];
                        pszProtPtrNames[iPowerUpVDH] = pszProtPtrNames[i];
                        pszRealPtrNames[iPowerUpVDH] = pszRealPtrNames[i];
                        i = 0;          /* Indicate PowerUp device found */
                        break;
                    }
                }

                DosUnloadVideoHandler(pszDeviceString[i]); /* Unload VDH */
                i++;
            }

            if (i) {                    /* PowerUp device not found */
                fFillDevBlk = fcFillLogicDeviceBlock;
                if (uInitGenericVDH()) { /* Load the generic VDH */
                    rc = ERROR_NO_DEVICE_INSTALLED;
                } else {
                    pszDeviceString[iPowerUpVDH] = pszGenericVDH;
                    pszProtPtrNames[iPowerUpVDH] = pszRealPtrNames[iPowerUpVDH]
                    = szDefPtrDev;      /* Default Names */
                }
            }
            iLastVDH = iPowerUpVDH;     /* only one VDH */

        } else {                        /* Fill logical device block */

            pszDeviceString[iPowerUpVDH] = pszDeviceString[0]; /* Restore  */
            pszProtPtrNames[iPowerUpVDH] = pszProtPtrNames[0]; /* original */
            pszRealPtrNames[iPowerUpVDH] = pszRealPtrNames[0]; /* names    */
                                        /* End code for @S16 */

            i = j = iPowerUpVDH;        /* start with PowerUp vector */
            while ((j <= cVideoDevices) && (i < cDevTblEntries)) {

                                        /* load the video driver */
                if (DosLoadVideoHandler(&ppcalltbl[i], pszDeviceString[j])) {
                    DosUnloadVideoHandler(pszDeviceString[j]); /* Unload VDH, @S16 */
                    pszDeviceString[j] = NULL; /* Remove failing devices */
                } else {
                    i++;
                }
                j++;
            }

            if (i == iPowerUpVDH) {     /* no devices successfully installed */

                fFillDevBlk = fcFillLogicDeviceBlock;
                if (uInitGenericVDH()) { /* Load the generic VDH */
                    rc = ERROR_NO_DEVICE_INSTALLED;
                } else {
                    cVideoDevices = iLastVDH = iPowerUpVDH; /* only one VDH */
                    pszDeviceString[iPowerUpVDH] = pszGenericVDH;
                    pszProtPtrNames[iPowerUpVDH] = pszRealPtrNames[iPowerUpVDH]
                    = szDefPtrDev;      /* Default Names */
                }

            } else {                    /* set the power up display */
                iLastVDH = i - 1;

                i = j = iPowerUpVDH;    /* Compress the device table */
                while ( (j <= cVideoDevices) && (i <= iLastVDH) ) {
                    if (pszDeviceString[j]) {
                        pszDeviceString[i] = pszDeviceString[j];
                        pszProtPtrNames[i] = pszProtPtrNames[j];
                        pszRealPtrNames[i] = pszRealPtrNames[j];
                        i++;
                    }
                    j++;
                }
                cVideoDevices = iLastVDH; /*B703657*/

                for (i = iPowerUpVDH; i < iLastVDH; i++) { /* query each display */
                    if (fIsPowerup(ppcalltbl[i])) {

                        pcalltblTmp = ppcalltbl[iPowerUpVDH];
                        ppcalltbl[iPowerUpVDH] = ppcalltbl[i];
                        ppcalltbl[i] = pcalltblTmp; /* Swap Call Tables */

                        pszTmp = pszDeviceString[iPowerUpVDH];
                        pszDeviceString[iPowerUpVDH] = pszDeviceString[i];
                        pszDeviceString[i] = pszTmp; /* Swap Devices */

                        pszTmp = ppszProtPtrNames[iPowerUpVDH];
                        ppszProtPtrNames[iPowerUpVDH] = ppszProtPtrNames[i];
                        ppszProtPtrNames[i] = pszTmp; /* Swap Prot Mode */

                        pszTmp = ppszRealPtrNames[iPowerUpVDH];
                        ppszRealPtrNames[iPowerUpVDH] = ppszRealPtrNames[i];
                        ppszRealPtrNames[i] = pszTmp; /* Swap Real Mode */

                        break;          /* Powerup found, stop searching */
                    }
                }
            }
        }                               /*@S16*/

    } else {                            /* Table filling not required */

        if ( (fShellInited) &&          /* the shell is already initialized */
            (bvsinstance.inst_sgnum > bvsglobal.cmn_maxbase) ) { /* @T45 */
            if (uLoadWinthornVDH(ppcalltbl)) { /*@S36*/
                rc = ERROR_VIO_INTERNAL_RESOURCE; /*@S36*/
            }                           /*@S36*/

        } else {                        /* @T45 */

            for (i = iPowerUpVDH; i <= iLastVDH; i++) {
                if (DosLoadVideoHandler(&ppcalltbl[i], pszDeviceString[i])) {
                    rc = ERROR_VIO_INTERNAL_RESOURCE; /* @@TB3 */
                    break;              /* @@TB3 */
                }                       /* @@TB3 */
            }
        }
    }
    RestoreRegs();
    return (rc);                        /* return success */
}

/*----------------------------------------------------------------------*/
/* End of new routines for DCR 261                              D261    */
/*----------------------------------------------------------------------*/

/***    uLoadWinthornVDH - load and initialize the AVIO VDH
*
*       This routine call DosLoadVideoDriver on the AVIO VDH.  If the
*       shell fails to load the VDH, mark the VDH as not used.
*
*       PSEUDOCODE
*
*           preset a good return code
*           if the generic VDH needs to be loaded
*               load the Winthorn VDH with DosLoadVideoHandler
*                   if the VDH failed to load and
*                      this is the shell init call
*                       mark the AVIO VDH as failing to load
*           return the error code
*
*       ENTRY   none
*
*       EXIT    returns - 0 if success
*                         error code otherwise
*/

unsigned near
uLoadWinthornVDH(ppcalltbl)
CALLTBL far ** ppcalltbl;
{
    unsigned rc;
    CALLTBL far *pcalltbl;

    rc = NO_ERROR;                      /* preset good error code */
    if (pszWindowBVH) {                 /* try to load the AVIO VDH, @S42*/
                                        /* load the VDH */
        fFillDevBlk = fcFillLogicDeviceBlock; /* DCR 203 @P1, @T15 */
        rc = DosLoadVideoHandler(&ppcalltbl[iWinVioVDH],
        pszWindowBVH);                  /*@S42*/

        if ((rc != NO_ERROR) &&         /* init of VDH failed */
            (!fShellInited)) {          /* this is the shell init call */
            pszWindowBVH = NULL;        /* do not try to load again,@S42*/
        }
    }
    return (rc);
}

/***    uInitGenericVDH - load and initialize the generic VDH
*
*       This routine calls DosLoadVideoDriver on the generic VDH
*
*       PSEUDOCODE
*
*           preset a good return code
*           if the generic VDH needs to be loaded
*               if the generic VDH call table needs to be initialized
*                   initialize the generic call table
*               if the generic VDH call table is initialized
*                   place the generic VDH call table in the power up location
*                   load the generic VDH with DosLoadVideoHandler
*                   if the VDH loaded successfully
*                       mark the generic VDH as loaded
*           return the error code
*
*       ENTRY   none
*
*       EXIT    returns - 0 if success
*                         error code otherwise
*/

unsigned near
uInitGenericVDH()
{
    unsigned rc = NO_ERROR;             /* preset good return code */
    CALLTBL far *pcalltbl;

    if (fIsGenericVDHInited == FALSE) { /* generic VDH not initialized */

        if (!(GlobalFlags & gf_initgenvec)) { /* vector needs to be initialized */
            if (fGetFreeCallTable(&pchGenCallTbl)) { /* @P1 */
                GlobalFlags |= gf_initgenvec; /* @P1,@S16 */
            }
        }

        pcalltbl  = pchGenCallTbl;      /* call vector */
        if (GlobalFlags & gf_initgenvec) { /* call vector is initialized */
            bvsglobal.cmn_calltables[iPowerUpVDH] = pcalltbl;
            if (pszGenericVDH == NULL) { /* Generic VDH not yet attempted */
                                        /* Try 8514, VGA, EGA, CGA, then Generic */
                if (rc=DosLoadVideoHandler(&pcalltbl,(pszGenericVDH=szGeneric8514))) { /*D261*/
                    DosUnloadVideoHandler(pszGenericVDH); /* unload generic VDH, D261*/
                    if (rc=DosLoadVideoHandler(&pcalltbl,(pszGenericVDH=szGenericVGA))) {
                        DosUnloadVideoHandler(pszGenericVDH); /* unload generic VDH */
                        if (rc=DosLoadVideoHandler(&pcalltbl,(pszGenericVDH=szGenericEGA))) {
                            DosUnloadVideoHandler(pszGenericVDH); /* unload generic VDH */
                            if (rc=DosLoadVideoHandler(&pcalltbl,(pszGenericVDH=szGenericCGA))) { /* @T15 */
                                DosUnloadVideoHandler(pszGenericVDH); /* unload generic VDH */       /* @T15 */
                                rc=DosLoadVideoHandler(&pcalltbl,(pszGenericVDH=szGenericVDH));
                            }           /* DCR 203 - @T15 */
                        }
                    }
                }

            } else {
                rc=DosLoadVideoHandler(&pcalltbl,pszGenericVDH);
            }
            if (rc == NO_ERROR) {       /* init of some Generic VDH succeeded */
                fIsGenericVDHInited = TRUE; /* record success */
            }
        }
    }
    return (rc);
}

/***    uErrorFunction - return an error
*
*       This routine returns an error.
*
*       PSEUDOCODE:
*
*           return (ERROR_FUNCTION_NOT_SUPPORTED)
*
*       ENTRY   none
*
*       EXIT    return - ERROR_FUNCTION_NOT_SUPPORTED)
*/

unsigned far pascal
uErrorFunction (pfParm1, pfParm2, ulParm3)
void far * pfParm1;
void far * pfParm2;
unsigned long ulParm3;
{
    return (ERROR_VIO_INVALID_PARMS);
}

/***    fIsPowerup - determine if the VDH is the powerup display
*
*       This routine takes the call table for a device, sets up for
*       the ReturnConfigInfo call and calls the routine.  If the call
*       succeedsl, the returned information is used to determine if
*       the device is the power up display.
*
*       PSEUDOCODE:
*
*           set up for the Return Config Info call in the VDH
*           call the Return Config Info routine in the VDH
*           if the call succeeds
*               if the VDH drives the powerup display
*                   return TRUE
*           return FALSE
*
*       ENTRY   pcalltbl    - pointer to the call table structure address
*
*       EXIT    return      - TRUE if the display is the powerup display
*                             FALSE otherwise
*/

unsigned near
fIsPowerup (pcalltbl)
CALLTBL far *pcalltbl;
{
    unsigned        rc;
    VDH_CONFIG      ParmBlock;
    VDHCONFIGINFO   ConfigBuf;


    ParmBlock.Length = sizeof (ParmBlock);
    ParmBlock.Flags = 0;
    ConfigBuf.cb = sizeof (ConfigBuf);
    ParmBlock.ConfigDataPTR = (VDHCONFIGINFO far *)&ConfigBuf;
    if (!(rc = (*pcalltbl)[fnRetConfigInfo-fn_BufferUpdate](NULL,
        (char far *)&ParmBlock,
        fnRetConfigInfo))) {            /*D261*/
        if (ConfigBuf.Flags & umaskPowerup) {
            return (TRUE);
        }
    }
    return (FALSE);                     /* This is not the power up display */

}

/***    DosLoadVideoHandler - get the call vector for a video device hander
*
*       This routine calls DosDeviceEnable routine with the Fill Logical
*       Device Block function.  It does the first call to this function
*       for each device actually initializes the call vector.  Subsequent
*       calls establish addressability and allow per process initialization
*       in the VDH.
*
*       PSEUDOCODE
*           set up the parameters for DosDevEnable
*           call DosDevEnable on the device
*           return the error code from DosDevEnable
*
*       ENTRY   pchCallVector   - pointer to the call table for the device
*               pszDeviceString - device string.
*
*       EXIT    pchCallVector   - initialized on the first call to the VDH
*               return          - 0 if success
*                                 otherwise errorcode
*/

unsigned near pascal
DosLoadVideoHandler ( ppcalltbl, pszDeviceString )
CALLTBL far ** ppcalltbl;
char far * pszDeviceString;
{
    unsigned i,rc;                      /*D261*/
    struct
    { unsigned long ulEngineVersion;
        unsigned long cVectorEntries;
    }   Parameter1;
    struct
    { unsigned long far * pmaskFillPhysDevBlk;
        CALLTBL far * pchVector;
    }   Parameter2;
    CALLTBL far *pcalltbl;

    SaveRegs();
    rc = ERROR_CANT_GET_CALL_TABLE;     /*D261,@S21*/
    if (fGetTempBuf((char far **)&pcalltbl,4*fn_NextAvail)) { /* Dummy table, D261 */
        if (!*ppcalltbl) {
            fGetFreeCallTable(ppcalltbl);
        }
        if (*ppcalltbl) {
            for (i=0; i<fn_NextAvail; i++) { /*D261*/
                (*pcalltbl)[i] = (i < fn_BufferUpdate) /*D261*/
                ? uErrorFunction : (**ppcalltbl)[i-fn_BufferUpdate]; /*D261*/
            }                           /* Initialize the dummy table, D261*/

                                        /* set up for the call to the */
                                        /* Fill Logical Device Block function */
            Parameter1.ulEngineVersion = ulGraphicsEngineVersion;
            Parameter1.cVectorEntries = cCallVectorEntries + fn_BufferUpdate; /*D261*/
            Parameter2.pmaskFillPhysDevBlk = (long far *) maskFillDevBlk;
            Parameter2.pchVector = pcalltbl; /*D261*/
                                        /* call the enable routine in the VDH */
            if (!(rc = DosDriverEnable( (char far *) pszDeviceString,
                ((unsigned long) (char far *) &Parameter2),
                ((unsigned long) (char far *) &Parameter1),
                fFillDevBlk,
                (char far *) szDefEntry,
                uVideoEnableCategory,
                uVideoEnableFunction))) {
                for (i=0; i<cCallVectorEntries; i++) { /*D261*/
                    (**ppcalltbl)[i] = (*pcalltbl)[i+fn_BufferUpdate]; /*D261*/
                }                       /* Copy dummy to master, D261*/
            }

        }
        vFreeTempBuf((char far *)pcalltbl);
    }

    RestoreRegs();
    return (rc);                        /* return the code from DosDriverEnable */
}

/***    DosDriverEnable - Call the enable routines in a device handler
*
*       This routine makes sure the dynlinks and devices for in a given
*       device handler are loaded.  Once the dynlinks are in the address
*       space of the calling process, the enable routine is called in
*       each of the components of the device handler.
*
*       PSEUDOCODE:
*
*           if the device handler data is not initialized
*               Initialize the device handler data  (fInitializeDeviceTable)
*               if initialization fails
*                   return error
*           Register the device (fRegisterDevice)
*           if registration fails
*               return error
*           preset the error code to NOT SUPPORTED
*           for each component of the device (fGetNextComponent)
*               call the enable routine for the component
*               if the return code is NOT_SUPPORTED
*                   ignore it
*               else
*                   set the  error code to the return code
*                   if the return code not NO_ERROR
*                       exit the for loop
*           return (error code)
*
*
*       ENTRY   pszDevice       - string defining the device handler
*               ulParm1         - variable parameter passed to DH
*               ulParm2         - variable parameter passed to DH
*               ulFunction      - enable function number passed to DH
*               pszDefEntryName - default dynlink enable entry point
*               uDefCategory    - default device enable IOCTL category
*               uDefFunction    - default device enable IOCTL function
*
*       EXIT    Parameter1      - depends on enable function called
*               Parameter2      - depends on enable function called
*               return          - if no error 0
*                                 otherwise error code
*
*/

unsigned near pascal
DosDriverEnable( pszDevice, ulParm2, ulParm1, ulFunction,
pszDefEntryName, uDefCategory, uDefFunction )

char far *      pszDevice;
unsigned long   ulParm2;
unsigned long   ulParm1;
unsigned long   ulFunction;
char far *      pszDefEntryName;
unsigned        uDefCategory;
unsigned        uDefFunction;

{
    register unsigned i;
    register unsigned rc;
    register unsigned rcDDE;
    unsigned uCat;
    unsigned uFunc;
    unsigned hTemp;
    unsigned uAction;
    t_dev far * pdevDev;
    char far *  pszComponent = NULL;
    char far *  pszTempEntry = NULL;
    struct
    {
        unsigned uLength;
        unsigned long ulFunction;
        unsigned long ulParameter1;
        unsigned long ulParameter2;
        unsigned uReturnCode;
    }   ParmBlock;

    if (!fPerProcDataValid) {           /* pre process dev table info invalid */
        for (i = 0; i < cDevTblEntries; i++ ) { /* each dev table entry */
            fDevValid[i] = FALSE;       /* mark the entry not valid */
        }
        fPerProcDataValid = TRUE;       /* pre process dev table info valid */
    }
                                        /* failed to register device */
    if (fRegisterDevice( &pdevDev, pszDevice, pszDefEntryName,
        uDefCategory, uDefFunction ) == FALSE) {
        return (ERROR_UNABLE_TO_ACCESS_DEVICE); /* indicate failure */
    }

    rcDDE = ERROR_FUNCTION_NOT_SUPPORTED; /* preset return code */

    if (!(fGetTempBuf(&pszComponent, cbMaxString) && /* failed to allocate */
        fGetTempBuf(&pszTempEntry, cbMaxString) ) ) { /* temporary buffers */
        vFreeTempBuf(pszComponent);
        vFreeTempBuf(pszTempEntry);
        return (ERROR_INSUFFICIENT_MEMORY);
    }
    for ( i = 0; i < pdevDev->cComponents; i++) { /* each component in dev */
                                        /* get the next component */
        fGetNextComponent ( &pszDevice, pszComponent,
        pszTempEntry, &uCat, &uFunc);
        if (pdevDev->cmpComponent[i].funcEnable != NULL) { /* dynlink */
            rc = (pdevDev->cmpComponent[i].funcEnable) /* call enable */
            (ulParm2,ulParm1,ulFunction); /* routine */
        } else {                        /* component is a device driver */
            rc = DOSOPEN (pszComponent, /* open device driver */
            (unsigned far *) &hTemp,
            (unsigned far *)&uAction, 0L, 0, 1, 0x42, 0L);
            if (rc == NO_ERROR) {       /* device is open */
                                        /* call enable function */
                rc = DOSDEVIOCTL(NULL,
                (char far *) &(ParmBlock.uLength),
                pdevDev->cmpComponent[i].uFunction,
                pdevDev->cmpComponent[i].uCategory,
                hTemp);
                if (rc == NO_ERROR) {   /* IOCTL succeeded */
                    rc = ParmBlock.uReturnCode; /* set the enable return code */
                }
                DOSCLOSE (hTemp);       /* close the device driver */
            } else {                    /* not a valid device */
                rc = ERROR_UNABLE_TO_ACCESS_DEVICE; /* return error */
            }
        }
                                        /* prepare the errorcode for the */
                                        /* combined enable routine */
        if (rc != ERROR_FUNCTION_NOT_SUPPORTED) { /* ignore not supported err */
            rcDDE = rc;                 /* set the global enable error code */
            if (rc != NO_ERROR) {       /* some real error occurred */
                break;                  /* stop calling device components */
            }
        }
    }
    vFreeTempBuf(pszComponent);
    vFreeTempBuf(pszTempEntry);
    return (rcDDE);                     /* return global enable error code */
}

/***    vInitializeEntry - initialize device handler table entry
*
*       This routine initializes all of the fields of a device table entry
*
*       PSEUDOCODE:
*
*           for (each component in the table entry)
*               initialize the component fields
*           initialize the other fields in the entry
*           return
*
*       ENTRY   pdevTable   - pointer to the entry to be initialized.
*
*       EXIT    none
*/

void near
vInitializeEntry(pdevTable)
t_dev far * pdevTable;
{
    register unsigned i;

    for (i = 0; i < cMaxComponent; i++) { /* each component in the device */
        pdevTable->cmpComponent[i].uCategory = 0; /* IOCTL category */
        pdevTable->cmpComponent[i].uFunction = 0; /* IOCTL function */
        pdevTable->cmpComponent[i].funcEnable = 0; /* DLL function address */
        pdevTable->cmpComponent[i].hModule = 0; /* DLL module handle */
    }
    pdevTable->cComponents = 0;         /* number of components in device */
    *(pdevTable->szDeviceName) = 0;     /* device string */
    *(pdevTable->szEntry) = 0;          /* default entry point name */
    pdevTable->uCategory = 0;           /* default IOCTL category */
    pdevTable->uFunction = 0;           /* default IOCTL function */
    pdevTable->fValid = FALSE;          /* entry is not in use */
    return ;
}

/***    fRegisterDevice - Load the device and add it to the device list
*
*       This routine searches for the device string in the list of known
*       device handlers.  If the device is not found, the device is loaded
*       and entered into the list of devices.  The calling process is
*       given addressability to the device if it does not already have it.
*       A pointer to the devices table entry is returned.
*
*       PSEUDOCODE:
*
*           search for the device in the list of known devices (fFindDevice)
*           if the device was not known
*               add the device to the list of known devices (fAddDevice)
*               if adding the device failed
*                   return FALSE
*           if the caller does not have addressability (fHasAddressability)
*               get the caller addressability (fMakeDevAddressable)
*               if the caller can not get addressability
*                   return FALSE
*           return TRUE
*
*
*       ENTRY   pszDevice   - string defining the device handler
*               ppdevTable  - pointer to the device information
*               pszEntry    - default entrypoint
*               uCategory   - default IOCTL category
*               uFunction   - default IOCTL function
*
*       EXIT    return      - TRUE if successful
*                             FALSE otherwise
*/

unsigned near
fRegisterDevice (ppdevTable, pszDevice, pszEntry, uCategory, uFunction )
t_dev far ** ppdevTable;
char far * pszDevice;
char far * pszEntry;
unsigned   uCategory;
unsigned   uFunction;

{
                                        /* locate the device in the table */
    if (fFindDevice (ppdevTable, pszDevice, pszEntry,
        uCategory, uFunction) == FALSE) { /* not found */
                                        /* add the device to the table */
        if (fAddDevice (ppdevTable, pszDevice, pszEntry, /* failed to add  */
            uCategory, uFunction) == FALSE) { /* device */
            return (FALSE);             /* return failure */
        }
    } else {                            /* device was found */
                                        /* verify process addressability */
                                        /* device not already in context */
        if (fHasAddressability(*ppdevTable) == FALSE) {
                                        /* could not get device in context */
            if (fMakeDevAddressable (ppdevTable)  == FALSE) {
                return (FALSE);         /* return failure */
            }
        }
    }
    return (TRUE);                      /* return success */
}

/***    fFindDevice - Find the device in the table of devices
*
*       This routine looks in the table of known devices for for a device
*       matching the given information.  If the device is found, a pointer
*       to the device table entry is returned.  If it is not, NULL is
*       returned in the device pointer.
*
*       PSEUDOCODE:
*
*           for (each entry in the table)
*               if (the entry contains a device)
*                   if (  the device string matches and
*                         the default entry name string matches and
*                         the default category matches and
*                         the default function matches )
*                       set the pointer to the device data
*                       return TRUE
*           set the pointer to NULL
*           return FALSE
*
*       ENTRY   ppdevTable  - pointer to the device information pointer
*               pszDevice   - string defining the device handler
*               pszEntry    - default entrypoint
*               uCategory   - default IOCTL category
*               uFunction   - default IOCTL function
*
*       EXIT   *ppdevTable  - pointer to the device information
*               return      - TRUE if successful
*                             FALSE otherwise
*/

unsigned near
fFindDevice (ppdevTable, pszDevice, pszEntry, uCategory, uFunction )
t_dev far ** ppdevTable;
char far * pszDevice;
char far * pszEntry;
unsigned   uCategory;
unsigned   uFunction;

{
    register unsigned i;

    if (pszDevice == NULL) {            /* @P1 */
        return (FALSE);                 /* return failure */             /* @P1 */
    }

    if (*pszDevice == 0) {              /* null device names are not allowed */
        return (FALSE);                 /* return failure */
    }
                                        /* linear search for a duplicate entry */
    for (i = 0; i < cDevGroups; i++) {  /* each entry in the table, @P1 */
        if (pDevGroups[i]->devEnt.fValid) { /* entry is in use, @P1 */
                                        /* the device string, default entry name */
                                        /* default category and function match */
            if ( (strcmp (pszDevice, pDevGroups[i]->devEnt.szDeviceName) == 0) &&
                (strcmp (pszEntry, pDevGroups[i]->devEnt.szEntry) == 0) &&
                (uCategory == pDevGroups[i]->devEnt.uCategory) &&
                (uFunction == pDevGroups[i]->devEnt.uFunction) ) {

                *ppdevTable = (t_dev far *)&(pDevGroups[i]->devEnt);
                return (TRUE);          /* return success */
            }
        }
    }
    *ppdevTable = NULL;                 /* pointer is not valid */
    return (FALSE);                     /* return failure */
}

/***    fAddDevice - Add a device to the list of devices and get adressability
*
*       This routine gets a free entry in the table of devices.  It fills in
*       the default information about the device, then proceeds to parse
*       the device string.  The IOCTL category and function are recorded for
*       the component.  Each component is loaded first as a device and
*       if that fails, it is loaded as a dynlink.  The type of the component
*       (dynlink or device driver) is noted.  If the component is a dynlink,
*       the address of the enable routine is gotten and saved.  If any of
*       the above fail, a NULL is returned for the table address, the table
*       entry is freed and FALSE is returned by the function.  If all of the
*       above succeed, a pointer to the table entry is set and TRUE is
*       returned by the function.
*
*       PSEUDOCODE:
*
*           get a free device table entry  (fGetFreeDevTableEntry)
*           if no free device table entrys
*               return FALSE
*           set the device string in the device table entry
*           set the default entry point name in the device table entry
*           set the default category in the device table entry
*           set the default function in the device table entry
*           for (each component in the device) (fGetNextComponent)
*               attempt to open it as a device (fIsDeviceDriver)
*               if opening it as a device succeeds
*                   save the category for the enable IOCTL
*                   save the function for the enable IOCTL
*               else
*                   attempt to load it as a dynlink
*                   if loading it fails
*                       free the device table entry
*                       return FALSE
*                   get the address of the enable entry point (fGetEnableEntry)
*                   if getting the address fails
*                       free the device table entry (vFreeDevTableEntry)
*                       return FALSE
*                   save the entry point address
*           set the address of the device table entry to be returned
*           return TRUE
*
*       ENTRY   pszDevice   - string defining the device handler
*               ppdevTable  - pointer to the device information
*               pszEntry    - default entrypoint
*               uCategory   - default IOCTL category
*               uFunction   - default IOCTL function
*
*       EXIT    return      - TRUE if successful
*                             FALSE otherwise
*/

unsigned near
fAddDevice (ppdevTable, pszDevice, pszEntry, uCategory, uFunction )
t_dev far ** ppdevTable;
char far * pszDevice;
char far * pszEntry;
unsigned   uCategory;
unsigned   uFunction;

{
    unsigned uCat;
    unsigned uFunc;
    char far *  pszComponent = NULL;
    char far *  pszTempEntry = NULL;
    unsigned (far pascal *funcTemp)();
    unsigned fLoadOK = TRUE;
    unsigned i;
    unsigned hModule;

    if (pszDevice == NULL) {            /* @P1 */
        return (FALSE);                 /* return failure */             /* @P1 */
    }

    if (*pszDevice == 0) {              /* null device strings are invalid */
        return (FALSE);                 /* return failure */
    }
    if (fGetFreeDevTableEntry(ppdevTable) == FALSE) { /* no free entrys */
        return (FALSE);                 /* return failure */
    }
    strcpy((*ppdevTable)->szDeviceName, pszDevice); /* save the device string */
    strcpy((*ppdevTable)->szEntry, pszEntry); /* save the entry name */
    (*ppdevTable)->uCategory = uCategory; /* save the default category */
    (*ppdevTable)->uFunction = uFunction; /* save the default category */
    if (!(fGetTempBuf(&pszComponent, cbMaxString) && /* failed to allocate */
        fGetTempBuf(&pszTempEntry, cbMaxString) ) ) { /* temporary buffers */
        vFreeTempBuf(pszComponent);
        vFreeTempBuf(pszTempEntry);
        return (FALSE);
    }
    uCat = uCategory;                   /* category for this component */
    uFunc = uFunction;                  /* function for this component */
    strcpy (pszTempEntry, pszEntry);    /* entry name for this component */
    i = 0;                              /* index of current component */
                                        /* step through the components of */
                                        /* the device handler */

    while ( fGetNextComponent ( &pszDevice, pszComponent,
        pszTempEntry, &uCat, &uFunc) ) {

                                        /* the component is a device driver */
        if (fIsDeviceDriver(pszComponent)) {
            (*ppdevTable)->cmpComponent[i].uCategory = uCat; /* enable cat */
            (*ppdevTable)->cmpComponent[i].uFunction = uFunc; /* enable func */
        } else {                        /* the component is a dynlink */
            if (fGetEnableEntry(&funcTemp,&hModule,pszComponent,pszTempEntry)) {
                                        /* module handle and enable function */
                (*ppdevTable)->cmpComponent[i].hModule = hModule;
                (*ppdevTable)->cmpComponent[i].funcEnable = funcTemp;
            } else {                    /* component is not valid */
                fLoadOK = FALSE;        /* note device failed to load */
                break;                  /* exit the while loop */
            }
        }                               /* component successfully loaded */
        uCat = uCategory;               /* set default enable category */
        uFunc = uFunction;              /* set default enable function */
        strcpy (pszTempEntry, pszEntry); /* set default enable function name */
        i++;                            /* advance to next component */
    }
    if (fLoadOK == FALSE) {             /* device failed to load */
        vFreeDevTableEntry(*ppdevTable); /* release device table entry */
        *ppdevTable = NULL;             /* device pointer is invalid */
    } else {                            /* device successfully loaded */
        fDevValid[(*ppdevTable)->iDevice] = TRUE; /* mark entry valid */
        (*ppdevTable)->cComponents = i; /* save the number of components */
    }
    vFreeTempBuf(pszComponent);
    vFreeTempBuf(pszTempEntry);
    return (fLoadOK);                   /* indicate success or failure */
}

/***    fGetNextComponent - get the next component from the device string
*
*       This routine reads the string describing a video device handler
*       and returns the values associated with the device tag, the real
*       mode pointer draw tag and the protect mode pointer draw tag.
*
*       PSEUDOCODE
*
*           clear the beginning of the string of separators
*           if the end of the device string has been reached
*               return FALSE
*           while not end of string and not a component or parameter separator
*               copy characters into the component name
*           if we stopped copying characters for a parameter separator
*               step past the separator
*               while not end of string and not a component or
*                           parameter separator
*                   copy characters into the entry point name
*               if we stopped copying characters for a parameter separator
*                   convert the entry name to an integer and save it as
*                   the category number
*                   step past the separator
*                   while not end of string and not a component or
*                               parameter separator
*                       copy characters into the entry point name
*                   if we stopped copying characters for a parameter separator
*                       return FALSE
*                   convert the entry name to an integer and save it as
*                   the function number
*           return TRUE
*
*
*       ENTRY   ppszDevice    - pointer to the device string
*               pszComponent  - string in which to return the component name
*               pszEntry      - pointer in which to return the entry name
*               puCat         - pointer to the IOCTL category for the component
*               puFunc        - pointer to the IOCTL category for the component
*
*       EXIT    returns - TRUE if a component was found
*                         FALSE otherwise
*/

unsigned near
fGetNextComponent ( ppszDevice, pszComponent, pszEntry, puCat, puFunc)
char far ** ppszDevice;
char far * pszComponent;
char far * pszEntry;
unsigned * puCat;
unsigned * puFunc;
{
    char    *pszTemp;

    while (**ppszDevice == chComponentSep) { /* clear leading separators */
        (*ppszDevice)++;
    }
    if (**ppszDevice == 0) {            /* end of device string */
        return (FALSE);                 /* indicate failure */
    }
                                        /* collect the next component name */
                                        /* char is not end or separator */
    while ((**ppszDevice != 0) && (**ppszDevice != chComponentSep)
        && (**ppszDevice != chParameterSep))  {
        *pszComponent++ = *(*ppszDevice)++; /* add the char to the string */
    }
    *pszComponent = 0;                  /* null terminate the string */

                                        /* IOCTL or entry name change */
    if (**ppszDevice == chParameterSep) { /* assume entry name */

        (*ppszDevice)++;                /* advance past separator */
        pszTemp = pszEntry;             /* set temporary pointer */
                                        /* char is not end or separator */
        while ((**ppszDevice != 0) && (**ppszDevice != chComponentSep)
            && (**ppszDevice != chParameterSep)) {
            *pszTemp++ = *(*ppszDevice)++; /* add char to the string */
            *pszTemp = 0;               /* null terminate the string */
        }
        if (**ppszDevice == chParameterSep) { /* IOCTL cat and func change */
            *puCat = atoi(pszEntry);    /* make first parameter the category */
            pszTemp = pszEntry;         /* set temporary pointer */
            (*ppszDevice)++;            /* advance past the separator */
                                        /* char is not end or separator */
            while ((**ppszDevice != 0) && (**ppszDevice != chComponentSep)
                && (**ppszDevice != chParameterSep))  {
                *pszTemp++ = *(*ppszDevice)++; /* add char to the string */
                *pszTemp = 0;           /* null terminate the string */
            }
            *puFunc = atoi(pszEntry);   /* make the second parm the function */
            if (**ppszDevice == chParameterSep) { /* third parameter */
                return (FALSE);         /* indicate failure */
            }
        }
    }
    return (TRUE);                      /* indicate success */
}


/***    fIsDeviceDriver - determine if a name is a device driver
*
*       This routine scans the name of the component to see if it
*       begins with "\DEV\".  If it does, it opens and closes the
*       the name to see if it is valid as a device.  If it successfully
*       opens and closes, TRUE is returned, otherwise FALSE is returned.
*
*       PSEUDOCODE:
*
*           if the length of the component name > "\DEV\"
*               compare "\DEV\" with the prefix of the component name
*               if "\DEV\" is the prefix of the component name
*                   DOSOPEN(component name)
*                   if the open was successful
*                       DOSCLOSE the component
*                       return TRUE
*           return FALSE
*
*       ENTRY   pszComponent - string defining the device handler
*
*       EXIT    return       - TRUE if component is a device
*                              FALSE otherwise
*/

unsigned near
fIsDeviceDriver (pszComponent)
char far * pszComponent;
{
    register unsigned i;
    unsigned rc;
    unsigned hTemp;
    unsigned uAction;

    if (strlen(pszComponent) > cbDevicePrefix) { /* long enough to be a device */
                                        /* component has device prefix */
        if (strnicmp (szDevPrefix, pszComponent, cbDevicePrefix) == 0) {
            rc = DOSOPEN (pszComponent, (unsigned far *) &hTemp,
            (unsigned far *)&uAction,
            0L, 0, 1, 0x42, 0L);        /* see if device exists */
            if (rc == NO_ERROR) {       /* device opened successfully */
                rc = DOSCLOSE (hTemp);  /* close the device */
                if (rc == NO_ERROR) {   /* device closed successfully */
                    return (TRUE);      /* indicate component is a device */
                }
            }
        }
    }
    return (FALSE);                     /* indicate component is not a device */
}

/***    fGetEnableEntry - get the enable entry point of a dynlink
*
*       This routine calls DosLoadModule on the component name.  If
*       that succeeds, it does a DosGetProcAddr on the routine specified
*       as the enable entry point.  If both of the above succeed, TRUE
*       is returned, otherwise FALSE is returned.
*
*       PSEUDOCODE:
*
*           load the component (DOSLOADMODULE)
*           if the component loaded OK
*               get the address of the enable entry point (DOSGETPROCADDR)
*               if address was returned OK
*                   return TRUE
*               else
*                   free the module (DOSFREEMODULE)
*           return FALSE
*
*       ENTRY   pfuncEnable  - pointer to enable function pointer
*               pszComponent - string naming the component
*               pszEntry     - string naming the enable entry point
*
*       EXIT   *pfuncEnable  - the address of the enable function
*               return       - TRUE if success
*                              FALSE otherwise
*/

unsigned near
fGetEnableEntry(pfuncEnable, phModule, pszComponent, pszEntry)
PEF *pfuncEnable;
unsigned far *  phModule;
char far * pszComponent;
char far * pszEntry;
{
    unsigned rc;
    char far *pszProblem = NULL;

    if (!fGetTempBuf(&pszProblem, cbMaxString)) { /* failed to allocate */
        return (FALSE);
    }
    *pszProblem = '\0';                 /* null terminate the error string */
                                        /* load the dynlink */
    rc = DOSLOADMODULE ( (char far *)pszProblem, cbMaxString,
    (char far *)pszComponent, phModule);
    if (rc == NO_ERROR) {               /* dynlink loaded successfully */
                                        /* get enable routine address */
        rc = DOSGETPROCADDR (*phModule, pszEntry,
        (unsigned long far *) pfuncEnable);
        if (rc == NO_ERROR) {           /* got the enable routine address */
            vFreeTempBuf(pszProblem);   /* free temporary storage */
            return (TRUE);              /* indicate success */
        } else {                        /* enable entry point not found */
            rc = DOSFREEMODULE (*phModule); /* release the dynlink */
        }
    }

    vFreeTempBuf(pszProblem);           /* free temporary storage */
    return (FALSE);                     /* indicate failure */
}

/***    fHasAddressability - determine if DH is in processes context
*
*       This routine checks a flag in the processes per process data.
*       One flag is kept for each entry in the device table.  We may
*       want to make these counts to allow the device table to reuse
*       its entries.
*
*       PSEUDOCODE:
*
*           return the flag from the device table entry
*
*       ENTRY   iDevTable   - the index of the entry in question
*
*       EXIT    return      - TRUE if the process has adressability
*                             FALSE otherwise
*/

unsigned near
fHasAddressability (pdevEntry)
t_dev far * pdevEntry;
{
    return (fDevValid[pdevEntry->iDevice]); /* flag indicates device is valid */
}


/***    fMakeDevAddressable - bring a DH into a processes context
*
*       This routine looks at each component of the device handler
*       in turn.  For each component that is a dynlink, a DosLoadModule
*       is called on that component.  If any of the dynlink components
*       fail to load, the loaded modules are unloaded and FALSE is
*       returned.  If all dynlinks succeed in loading, TRUE is returned.
*
*       PSEUDOCODE:
*
*           set the device string from the device table entry
*           set the default entry point name from the device table entry
*           set the default category from the device table entry
*           set the default function from the device table entry
*           for (each component in the device) (fGetNextComponent)
*               if the component is a dynlink
*                   attempt to load it as a dynlink (DOSLOADMODULE)
*                   if loading it fails
*                       for (each component up to this one)
*                           if the component is a dynlink
*                               unload it  (DOSFREEMODULE)
*                       return FALSE
*           return TRUE
*
*       ENTRY   ppdevTable  - pointer to the device information
*
*       EXIT    return      - TRUE if successful
*                             FALSE otherwise
*/

unsigned near
fMakeDevAddressable (ppdevTable)
t_dev far ** ppdevTable;
{
    unsigned uCat;
    unsigned uFunc;
    unsigned rc;
    char far * pszDevice;
    char far * pszProblem = NULL;
    char far * pszComponent = NULL;
    char far * pszTempEntry = NULL;
    unsigned i;
    unsigned iUnload;

    if (!(fGetTempBuf(&pszProblem, cbMaxString) && /* failed to allocate */
        fGetTempBuf(&pszComponent, cbMaxString) && /* temporary buffers */
        fGetTempBuf(&pszTempEntry, cbMaxString) ) ) {

        vFreeTempBuf(pszProblem);
        vFreeTempBuf(pszComponent);
        vFreeTempBuf(pszTempEntry);
        return (FALSE);
    }

    pszDevice = (*ppdevTable)->szDeviceName; /* get pointer to device string */
    strcpy(pszTempEntry, (*ppdevTable)->szEntry); /* copy default entry name */
    uCat = (*ppdevTable)->uCategory;    /* get the default IOCTL category */
    uFunc = (*ppdevTable)->uFunction;   /* get the default IOCTL function */
    *pszProblem = '\0';                 /* terminate the error string */
    i = 0;                              /* index of current component */
                                        /* each component in the device */
    while ( fGetNextComponent ( &pszDevice, pszComponent,
        pszTempEntry, &uCat, &uFunc) == TRUE) {
                                        /* component is a dynlink */

        if ((*ppdevTable)->cmpComponent[i].funcEnable != NULL) {
                                        /* load the dynlink */

            rc = DOSLOADMODULE ( pszProblem, cbMaxString,
            pszComponent, (unsigned far *)
            &((*ppdevTable)->cmpComponent[i].hModule));

            if (rc != NO_ERROR) {       /* the load failed */
                                        /* unload the ones that loaded */
                for (iUnload = 0; iUnload < i; iUnload++ ) { /* each module */
                    rc = DOSFREEMODULE ( /* free the module */
                    (*ppdevTable)->cmpComponent[iUnload].hModule);
                }
                vFreeTempBuf(pszProblem);
                vFreeTempBuf(pszComponent);
                vFreeTempBuf(pszTempEntry);
                return (FALSE);         /* indicate failure */
            }
        }
        strcpy(pszTempEntry, (*ppdevTable)->szEntry); /* default entry name */
        i++;
    }
    fDevValid[(*ppdevTable)->iDevice] = TRUE; /* mark device valid */
    vFreeTempBuf(pszProblem);
    vFreeTempBuf(pszComponent);
    vFreeTempBuf(pszTempEntry);
    return (TRUE);                      /* indicate success */
}

/*@C1  = 01/26/89 CJJ, Check if pointer is not NULL before freeing    P3736  */
/*@T15 = 03/03/89 TPL, DCR 203 changes, P3730 */
/* @@TB3 = 03/16/89 TLB - Exit with internal resource error if error    */
/*                        returned from DosLoadVideoHandler PTR B700462 */
/*@T19 = 03/16/89 TPL, Avoid loading AVIO at boot time, P6741 */
/*@P1  = 03/25/89  PL, DCR 132 changes */
/*JOPT = 03/30/89 STJ, Convert all local routines to NEAR subroutines   */
/*D198 = 03/31/89 STJ, Install BvsPrtSc and BvsWrtTTY as default VDH routines */
/*D261 = 04/21/89 STJ, Use CONFIG.SYS instead of OS2.INI and optimize   */
/*B702118 = 05/01/89 STJ, Don't record device until Env Var found */
/*@S10 = 05/11/89 STJ, Retain separate ENVB/LVB for each config, B701174 */
/*B703657 = 06/16/89 STJ, Reset count of devices after succesful loading */
/*@S16 = 06/26/89 STJ, Retry loading all VDHs at Shell Init time, HZ00235 */
/*@S21 = 07/28/89 STJ, Set return code for failed allocation */
/*@T45 = 08/31/89 TPL, Don't load other BVHs when starting VIO Wndw, B707495 */
/*@S36 = 10/11/89 STJ, Return failure of uLoadWinthorn, AR05111 */
/*@S42 = 11/08/89 STJ, Add VIDEO_WINDOW support */
