/*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.                                */
/*                                                                           */
/*****************************************************************************/
// initterm.c
// per-process DLL initialization and termination

#define INCL_DOS
#define INCL_ERRORS
#define INCL_PM
#include <os2.h>

#define INCL_32
#define INCL_VMANDDI
#include <ddi.h>

#define INCL_GREALL
#define INCL_GRE_DEVICESURFACE
#include <pmddi.h>

// c includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <builtin.h>


#define INCL_GENPLIB_MEMORY
#define INCL_GENPLIB_JOURNAL
#include <genplib.h>



#include "def.h"
#include "driver.h"
#include "funcs.h"









// ------------------------------------------------------------------------------------------------------------------
// _DLL_InitTerm is called once at process init, and once at process terminate
// ulFlag == 0, then do DLL init per process
// ulFlag == 1, then DLL freed per process


ULONG APIENTRY  _DLL_InitTerm( ULONG hThisModule, ULONG ulFlag )
{
  int              i;
  APIRET           rc;
  PPIB             pPib;
  PTIB             pTib;
  REGREC           regrec;
  ULONG            ulException;
  ULONG            ulrc;
  HMODULE          hmod;
  char             ach[ 8 ];


  REGISTERHANDLER( regrec );
  ulException = setjmp( regrec.jmp );
  if( ulException ) {
    // clean up here as required
    // check for the killed-thread case
    switch( ulException ) {
    case XCPT_PROCESS_TERMINATE:
    case XCPT_ASYNC_PROCESS_TERMINATE:
      DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    // error result
    ulrc = 0;
    goto depart;
  }



#ifdef KDB
  // hard-coded breakpoint for debug kernel version
  // when it hits, patch it to NOP:     E EIP 90
  // or perhaps set a breakpoint:       BR E OS2_PM_DRV_ENABLE
  // or ignore it and go:               G
  _interrupt(3);
#endif


  switch( ulFlag ) {
  case 0:
    // process is instantiating the driver
    DBPRINTF(( "DLL_InitTerm - instantiating\n" ));


    // get process id and store in process data area
    rc = DosGetInfoBlocks( &pTib, &pPib );
    assert( rc == 0 );
    procdata.pid = pPib->pib_ulpid;


    // SYSTEM-GLOBAL MUTEX SEMAPHORE

    // two chances to open the shared mutex sem;
    // necessary due to possible race condition where two processes
    // could be loading the driver at nearly the same time
    i = 2;
    do {
      // open the shared mutex sem for this process
      rc = DosOpenMutexSem( globals.pszGlobalSemName, &procdata.hmtxGlobalSem );
      switch( rc ) {
      case 0:
        // good result; sem  opened
        break;
      case ERROR_SEM_NOT_FOUND:
        // perhaps never been created? quite possible.
        // create a named (therefore shared) system, mutex sem in the unowned state;
        // in a race condition, this could fail, too, because other process could
        // have just created it. In this case, loop and try to open again
        rc = DosCreateMutexSem( globals.pszGlobalSemName, &procdata.hmtxGlobalSem, 0, 0 );
        break;
      default:
        // blow up the debug version
        assert( FALSE );
        break;
      }
    } while( rc && --i );
    assert( rc == 0 );


    // GLOBALS INITIALIZATION

    // request the global mutex sem
    rc = DosRequestMutexSem( procdata.hmtxGlobalSem, SEM_INDEFINITE_WAIT );
    assert( rc == 0 );
    if( !globals.hModule ) {
      // This is the first process instantiating the driver.
      // record module handle
      globals.hModule = hThisModule;
      assert( globals.hModule );

      // count the number of times init'ed and term'ed; this is the first
      assert( 0 == globals.cInstances );

      // Get function pointer for SetDeviceSurface.
      // PMGRE.DLL is a forwarder DLL to PMMERGE.DLL in Personal OS/2
      rc = DosLoadModule( ach, sizeof(ach)-1, "PMGRE", &hmod );
      assert( rc == 0 );

      // This call to DosQueryProcAddr fails on OS/2 2.1, but it does not prevent
      // you from building and loading the driver on OS/2 2.1.  The DRVNAMES utility
      // loads the driver in order to query the supported device names and to
      // place them in the .EXPAND extended attribute.
      // See pfnSetDeviceSurface check in enable.c.
      rc = DosQueryProcAddr( hmod, 0, "SETDEVICESURFACE", &globals.pfnSetDeviceSurface );
      // assert( 0 == rc );

      // alloc a shared heap
      globals.hmcbSharedHeap = GplMemoryCreateInstance( LEN_SHAREDHEAP, 0, 0, SHARED_MEMORY );
      assert( globals.hmcbSharedHeap );

      // get module name
      globals.pszModule = GplMemoryAlloc( globals.hmcbSharedHeap, CCHMAXPATH );
      assert( globals.pszModule );

      rc = DosQueryModuleName( globals.hModule, CCHMAXPATH, globals.pszModule );
      assert( rc == 0 );
    }

    // module handles are the same across all processes in the system
    assert( globals.hModule == hThisModule );

    // create a private process-wide sem for this process in the unowned state
    assert( (HMTX)0 == procdata.hmtxProcessSem  );
    rc = DosCreateMutexSem( NULL, &procdata.hmtxProcessSem, 0, 0 );
    assert( rc == 0 );

    // If this is the first process, the shared heap was created above.
    // If this is a subsequent process using the driver, this shared heap handle,
    // which resides on a shared page, is ready to use by this subsequent process
    // because GplMemoryCreateInstance uses SSAllocMem for shared heaps.  Memory
    // allocated with SSAllocMem is given/gotten by all processes that have
    // initialized the graphics engine, not just this driver.
    assert( globals.hmcbSharedHeap );

    // add a process
    globals.cInstances ++;

    rc = DosReleaseMutexSem( procdata.hmtxGlobalSem );
    assert( rc == 0 );
    break;


  case 1:
    // process is unlinking the driver; do cleanup
    DBPRINTF(( "DLL_InitTerm - de-instantiating\n" ));

    rc = DosRequestMutexSem( procdata.hmtxGlobalSem, SEM_INDEFINITE_WAIT );
    assert( rc == 0 );
    // decrement the driver instance count
    globals.cInstances--;

    if( globals.cInstances == 0 ) {
      // last process is unlinking the driver
      // this never happens on 2.0SP or 2.1 once a dc is created
      // it can happen at install time
      rc = GplMemoryDeleteInstance( globals.hmcbSharedHeap );
      assert( rc == 0 );
      globals.hmcbSharedHeap = (PVOID)NULL;
      globals.hModule = (HMODULE)0;
    }
    rc = DosReleaseMutexSem( procdata.hmtxGlobalSem );
    assert( rc == 0 );

    rc = DosCloseMutexSem( procdata.hmtxProcessSem );
    assert( rc == 0 );
    procdata.hmtxProcessSem = HMTX_CLOSED;

    rc = DosCloseMutexSem( procdata.hmtxGlobalSem );
    assert( rc == 0 );
    procdata.hmtxGlobalSem = HMTX_CLOSED;

    break;

  default:
    // blow up the debug version
    assert( FALSE );
  }


  // success
  ulrc = 1;


depart:
  UNREGISTERHANDLER( regrec );
  return ulrc;
}
