/*****************************************************************************
* Copyright (c) 1990-98 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/

/*++

Module Name:

   digisoh.c

Abstract:

   This module contains the NT OS entry points for the DigiSOH (Digi STREAMS On
   Host) driver.

Author:

   Ron Gredvig

--*/


////////////////////////////////////////////////////////////////////////////////


#include "digisoh.h"
#include "sohioctl.h"
#include "sohdstrm.h"
#include "sohcmplt.h"
#include "sohinitmp.h"
#include "sohbuild.h"
#include "sohreg.h"
#include "sohmsg.h"
#include "sohalloc.h"
#include "sohapi.h"

// Spider Streams specific include files
#include "types.h"
#include "stream.h"
#include "sohalloc.h"

#include "digilib.h"

#ifndef _DIGISTRM_DOT_C
#  define _DIGISTRM_DOT_C
static char     RCSInfo_DigiSohDotc[] = "$Header: /Components/Windows/NT/Sync/Build/DigiSoh/Src/digisoh.c 7     10/09/97 9:55a Ron $";
#endif


// Streams Load/Unload functions
extern BOOLEAN	shload(ULONG);
extern void		shunload();

//
// Global array of Stream object pointers for quick lookup via hObjectId
// Non-paged storage is allocated in DriverEntry
//
PSOH_STREAM_OBJECT	*gapStreamObj;
ULONG				gulMaxStreams;
SOH_RUNTIME_ENV		gSohRuntimeEnv;

//
// Prototypes which are required for an NT Device Driver
//
NTSTATUS
DriverEntry( IN PDRIVER_OBJECT DriverObject,
			 IN PUNICODE_STRING RegistryPath );

NTSTATUS
DigiSohCreate( IN PDEVICE_OBJECT DeviceObject,
			   IN PIRP Irp );

NTSTATUS
DigiSohCleanup( IN PDEVICE_OBJECT DeviceObject,
				IN PIRP Irp );

NTSTATUS
DigiSohClose( IN PDEVICE_OBJECT DeviceObject,
			  IN PIRP Irp );

NTSTATUS
DigiSohDeviceControl( IN PDEVICE_OBJECT DeviceObject,
					  IN PIRP Irp );

VOID
DigiSohUnload( IN PDRIVER_OBJECT pDriverObject );

NTSTATUS DigiSohDeviceShutdown( IN PDEVICE_OBJECT pDevice,
								IN PIRP           pIrp );

NTSTATUS
DigiSohInternalDeviceControl( IN PDEVICE_OBJECT DeviceObject,
							  IN PIRP Irp );

STATIC BOOLEAN DigiDontLoadDriver = FALSE;

#if !NT5_BUILD
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,DriverEntry)
#endif
#endif


NTSTATUS
InitStreamsRuntimeEnv( IN PDRIVER_OBJECT pDriverObject,
					   IN ULONG			 ulMaxStreams,
					   IN HANDLE		 hKeyDriver,
					   IN HANDLE		 hKeyDriverParameters,
					   ULONG			 ulPortCount

	)
/*++

Description:  Initialize the Streams Runtime Environment


--*/
{
	
	HANDLE          hDstreamThread;
	HANDLE          hIrpCompThread;
	NTSTATUS        Status = STATUS_UNSUCCESSFUL;
	NTSTATUS		iErrorStatus;
	int				iUniqueErrorCode;
	SOHALLOCTABLE   AllocTable;

	// Streams Resource Testing
#if DBG

	{
		ULONG	fTestResources;
		(void)DigiRegQueryDword( hKeyDriver,
							 L"TestResources",
							 &fTestResources,
							 DigiRegFlagOptional,
							 FALSE );

		if ( fTestResources )
		{
			SohAllocTest( &AllocTable, pDriverObject );
		}
	}

#endif //DBG
	
	InitializeListHead( &gSohRuntimeEnv.StreamObjectListHead );
	KeInitializeSpinLock( &gSohRuntimeEnv.SpinLock );
	KeInitializeEvent( &gSohRuntimeEnv.evStreams, SynchronizationEvent, FALSE );
	KeInitializeEvent( &gSohRuntimeEnv.evCompleter, SynchronizationEvent, FALSE );

	// Controlling Streams Access resources
	KeInitializeEvent( &gSohRuntimeEnv.evHandles, SynchronizationEvent, FALSE );
	KeInitializeSpinLock( &gSohRuntimeEnv.HandleSpinLock );
	gSohRuntimeEnv.bProcessingControlStreams = FALSE;
	gSohRuntimeEnv.bWaitProcessingControlStreams = FALSE;

	gSohRuntimeEnv.Type = SOH_OBJECT_TYPE_RUNTIME_ENV;
	
	// Allocate the stream object pool.
	if ( gapStreamObj = DigiAllocMem( NonPagedPool, sizeof( PVOID ) * ulMaxStreams ) )
	{
		RtlZeroMemory( gapStreamObj, sizeof( PVOID ) * ulMaxStreams );
	}
	else
	{
		// error handling
		iErrorStatus = SOH_INSUFFICIENT_RESOURCES;
		iUniqueErrorCode = 7;
		goto EXIT;
	}

	gSohRuntimeEnv.apStreamObj = gapStreamObj;

	// Set up the Streams Object Allocation Table
	SohAllocInitTable( &AllocTable, ulPortCount, hKeyDriverParameters, pDriverObject);

	// Initialize the Streams Objects
	Status = SohAllocInit( &AllocTable );

	if ( !NT_SUCCESS( Status ) )
	{

		// We must deallocate the Memory here or we cannot log the Error
		// The deallocate is also called by the EXIT, but the memory
		// will already be released.
		SohAllocUnload();

		// error handling
		iErrorStatus = SOH_INSUFFICIENT_NONPAGED_MEMORY;
		iUniqueErrorCode = 8;
		goto EXIT;
	}

	// Initialize the stream head module
	if (shload(gulMaxStreams) == FALSE) 
	{
		// error handling
		iErrorStatus = SOH_INSUFFICIENT_RESOURCES;
		iUniqueErrorCode = 9;
		goto EXIT;
	}

	// start DSTREAMS subsystem thread
	Status = PsCreateSystemThread( &hDstreamThread,
								   THREAD_ALL_ACCESS,
								   NULL,
								   NULL,
								   NULL,
								   SohStreams,
								   &gSohRuntimeEnv );

	if ( !NT_SUCCESS( Status ) )
	{
		iErrorStatus = SOH_THREAD_CREATION_PROBLEM;
		iUniqueErrorCode = 10;
		goto EXIT;
	}

	Status = ObReferenceObjectByHandle( hDstreamThread,
										THREAD_ALL_ACCESS,
										NULL,
										KernelMode,
										&gSohRuntimeEnv.pDstreamThreadObject,
										NULL );
	ZwClose(hDstreamThread);

	if ( !NT_SUCCESS( Status ) )
	{
		iErrorStatus = SOH_THREAD_CREATION_PROBLEM;
		iUniqueErrorCode = 11;
		goto EXIT;
	}

	// start IRP completion thread
	Status = PsCreateSystemThread( &hIrpCompThread,
								   THREAD_ALL_ACCESS,
								   NULL,
								   NULL,
								   NULL,
								   SohIrpCompleter,
								   &gSohRuntimeEnv );

	if ( !NT_SUCCESS( Status ) )
	{
		iErrorStatus = SOH_THREAD_CREATION_PROBLEM;
		iUniqueErrorCode = 12;
		goto EXIT;
	}

	Status = ObReferenceObjectByHandle( hIrpCompThread,
										THREAD_ALL_ACCESS,
										NULL,
										KernelMode,
										&gSohRuntimeEnv.pIrpCompThreadObject,
										NULL );
	ZwClose(hIrpCompThread);

	if ( !NT_SUCCESS( Status ) )
	{
		iErrorStatus = SOH_THREAD_CREATION_PROBLEM;
		iUniqueErrorCode = 13;
		goto EXIT;
	}

EXIT:

	if ( !NT_SUCCESS( Status ) )
	{
		// error handling
		DigiDump( DIGIERRORS, ( "SOH: Thread Creation Problem, %d, %x, %x\n", iUniqueErrorCode, iErrorStatus, Status ) );

		DigiLogError( pDriverObject,
					  iErrorStatus,
					  iUniqueErrorCode,
					  NULL,0,
					  NULL,0,
					  NULL,0 );

	}

	return Status;

}



void
ShutdownStreamsRuntimeEnv(
		BOOLEAN bUloadResources
	)
/*++

Description:  Destroy the Streams Runtime Environment


--*/
{
	
	// ShutDown the Stream thread if was started
	if (gSohRuntimeEnv.pDstreamThreadObject)
	{
		gSohRuntimeEnv.Unloading = TRUE;

		// set event for Stream thread
		KeSetEvent( &gSohRuntimeEnv.evStreams,
					0,
					FALSE );

		// Wait for the Thread to Complete		
		KeWaitForSingleObject( gSohRuntimeEnv.pDstreamThreadObject,
							   Executive,
							   KernelMode,
							   FALSE,
							   NULL );		
		ObDereferenceObject( gSohRuntimeEnv.pDstreamThreadObject );


	}

	// ShutDown the Completion thread if was started
	if (gSohRuntimeEnv.pIrpCompThreadObject)
	{
		gSohRuntimeEnv.Unloading = TRUE;

		// set event for completer thread
		KeSetEvent( &gSohRuntimeEnv.evCompleter,
					0,
					FALSE );

		// Wait for the Thread to Complete		
		KeWaitForSingleObject( gSohRuntimeEnv.pIrpCompThreadObject,
							   Executive,
							   KernelMode,
							   FALSE,
							   NULL );
		ObDereferenceObject( gSohRuntimeEnv.pIrpCompThreadObject );

	}

	// When the system is being shutdown it is not necessary to unload the
	// resources, in fact, we don't gracefully shutdown the streams stacks 
	// during shutdown, so all the resources are not on the resource queues
	// during shutdown.
	if (bUloadResources)
	{				
		// Unload the stream head module
		shunload();

		// Deallocate Streams Memory
		SohAllocUnload();
	}

}


NTSTATUS
DriverEntry( IN PDRIVER_OBJECT pDriverObject,
			 IN PUNICODE_STRING pRegistryPath )
/*++

Description:

Entry point for loading driver.

Arguments:

pDriverObject - Pointer to this driver's object.

pRegistryPath - Pointer to a unicode string containing this drivers registry
entry. (HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\[DriverName])

Return Value:

STATUS_SUCCESS - If the driver was successfully loaded, otherwise, a value
which indicates why it wasn't able to load.

Comments:

This routine initializes the Driver Object. Then it creates the Device Object
and schedules the STREAMS Subsystem thread and IRP Completion Thread. Finally
it opens and configures controlling Streams for Wan Links as defined in the
registry.

--*/
{
	PDEVICE_OBJECT  pDevObj	= NULL;
	PSOH_DEV_EXT    pDevExt	= NULL;

	UNICODE_STRING  ucRegDeviceName = { 0 };
	UNICODE_STRING  ucNTDeviceName = { 0 };
	UNICODE_STRING  ucDosDeviceName = { 0 };

	UNICODE_STRING  ucParametersPath = { 0 };

	ULONG           PortCount;

	ULONG           fBuildProtocolStacks	= TRUE;

	NTSTATUS        Status;
	HANDLE			hKeyDriver				= NULL;
	HANDLE			hKeyDriverParameters	= NULL;

	DigiDump( DIGIINIT, ( "DigiSoh: Entering DriverEntry\n" ) );
	DigiDump( DIGIINIT, ( "         pRegistryPath = %wZ\n", pRegistryPath ) );

	// Set up the debug globals
	DigiDebugInit( pRegistryPath,
		(DIGIBUGCHECK|DIGIERRORS|DIGIFLOW|DIGIINIT|DIGINOTIMPLEMENTED|DIGIVERYLOUD|DEBUG_DIGIPNP),
//		 0,
		 DEBUGMODE_ANY
		 );

	// Set by the Call to DigiDebugInit
	if( DigiDontLoadDriver )
	{
		Status = STATUS_CANCELLED;
		goto EXIT;
	}

	// Assign tag for memory allocated with DIGIAllocMem
	DigiInitMem( ' HOS' );

	// Initialize DigiMemPrint
//	MemPrintInitialize( "\\SystemRoot\\" WANLINKS_NAME_PREFIX_LC_QUOT "soh.log");

	// Open the Key
	Status = DigiRegOpenKey( &hKeyDriver, pRegistryPath->Buffer );
	if ( NT_ERROR( Status ) ) {

		// error processing
		DigiDump( DIGIERRORS,
			      ( "SOH: Couldn't open the driver key %wZ, Status = %x \n", pRegistryPath, Status ) );

/*
   For Windows 2000, the driver entry will be called even after the adapter has been removed
   So, don't send this to the event log, just bail...

		DigiLogError( pDriverObject,
					  SOH_UNABLE_TO_OPEN_KEY,
					  1,
					  NULL,0,
					  pRegistryPath->Buffer, pRegistryPath->Length + sizeof( WCHAR ),
					  NULL,0 );
*/

		goto EXIT;
	}

#if DBG

	{

		ULONG           fBreak = FALSE;

		// Retreive debug settings from registry. The destination isn't modified if
		// the value isn't in the registry. That means defaults can be set in the
		// driver but overridden by registry entries.
		(void)DigiRegQueryDword( hKeyDriver,
								 L"DebugBreak",
								 &fBreak,
								 DigiRegFlagOptional,
								 FALSE );

		if ( fBreak ) {
			DbgBreakPoint(  );
		}

	}

#endif

	// Obtain a UNICODE STRING long enough to add the Parameters Key to the input registry path.
	Status = DigiStrAllocUnicodeString ( &ucParametersPath,
									     pRegistryPath->Buffer,
						                 DIGISOH_SLASH,
									     DIGISOH_REGSUBKEY_PARAMETERS);
	
	if ( !NT_SUCCESS(Status) )
	{
		DigiDump(DIGIINIT,("DigiSoh: Couldn't Allocate the Parameters String, Status = %x \n", Status));

		DigiLogError( pDriverObject,
					  SOH_INSUFFICIENT_RESOURCES,
					  2,
					  NULL,0,
					  NULL,0,
					  NULL,0 );

		goto EXIT;
	}

	// Open the Driver Parameters Key
	Status = DigiRegOpenKey( &hKeyDriverParameters, ucParametersPath.Buffer );
	if ( NT_ERROR( Status ) ) {

		// error processing
		DigiDump( DIGIERRORS,
			      ( "SOH: Couldn't open the parameters %wZ \n", &ucParametersPath ) );

		DigiLogError( pDriverObject,
					  SOH_UNABLE_TO_OPEN_KEY,
					  3,
					  NULL,0,
					  DIGISOH_REGSUBKEY_PARAMETERS, sizeof DIGISOH_REGSUBKEY_PARAMETERS + sizeof( WCHAR ),
					  NULL,0 );

		goto EXIT;
	}

	// Initialize Driver Object
	pDriverObject->DriverUnload							= DigiSohUnload;
	pDriverObject->MajorFunction[IRP_MJ_CREATE]			= DigiSohCreate;
	pDriverObject->MajorFunction[IRP_MJ_CLOSE]			= DigiSohClose;
	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DigiSohDeviceControl;
	pDriverObject->MajorFunction[IRP_MJ_CLEANUP]		= DigiSohCleanup;
	pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN]       = DigiSohDeviceShutdown;

#if NT5_BUILD

	// Call the DStreams PnP Library Initialization
	{
		
		PNP_INIT_DATA	PnpInitData;

		wcscpy( PnpInitData.BusName,	L"Soh\\" );

		// Set the FDO call back routines
		PnpInitData.FdoData.CallbackStartDevice			= SohPnpStartDevice;
		PnpInitData.FdoData.CallbackStopDevice			= SohPnpStopDevice;
		PnpInitData.FdoData.CallbackRemoveDevice		= SohPnpRemoveDevice;
		PnpInitData.FdoData.CallbackQueryRemoveDevice	= SohPnpQueryRemoveDevice;

		// Set the PDO call back routines

		// Initialize the PnP library for this driver
		DsPnpInitialize(pDriverObject,&PnpInitData);

		// Set the Plug and Play Dispatch Routines.
		pDriverObject->MajorFunction[IRP_MJ_PNP]    = SohDevicePnp;
		pDriverObject->MajorFunction[IRP_MJ_POWER]  = SohDevicePower;

		pDriverObject->DriverExtension->AddDevice	= SohAddDevice;

	}
	
	// Get the port count from the registry if it is there.
	// The port count is used in determining how many streams
	// resources to build.  For NT5 this is dynamic, so we picked
	// a default and give the user a chance to change it via a
	// registry setting.
	(void)DigiRegQueryDword( hKeyDriverParameters,
							 DIGISOH_REGVAL_PORT_COUNT,
							 &PortCount,
							 DigiRegFlagOptional,
							 DIGISOH_REGVAL_PORT_COUNT_DEFAULT );


#else

	// initialize the Unicode strings
	RtlInitUnicodeString( &ucRegDeviceName, DEVICE_NAME );
	RtlInitUnicodeString( &ucNTDeviceName, NULL );
	RtlInitUnicodeString( &ucDosDeviceName, NULL );

	// Create full NT namespace name for this device (e.g. \Device\DigiSoh)
	Status = DigiStrDeviceName( &ucNTDeviceName,
								ucRegDeviceName.Buffer);

	if ( !NT_SUCCESS( Status ) )
	{
		// error handling
		DigiDump( DIGIERRORS, ( "SOH: Couldn't get space for the device name.\n" ) );

		DigiLogError( pDriverObject,
					  SOH_INSUFFICIENT_RESOURCES,
					  4,
					  NULL,0,
					  NULL,0,
					  NULL,0 );

		goto EXIT;
	}

	// create the device object
	Status = IoCreateDevice( pDriverObject,
							 sizeof( SOH_DEV_EXT ),
							 &ucNTDeviceName,
							 FILE_DEVICE_DS,
							 0,
							 FALSE,
							 &pDevObj );

	if ( !NT_SUCCESS( Status ) )
	{
		// error processing
		DigiDump( DIGIERRORS,
				  ( "SOH: Couldn't create device %wZ \n", &ucNTDeviceName ) );

		DigiLogError( pDevObj,
					  SOH_CREATE_DEVICE_FAILED,
					  5,
					  NULL,0,
					  ucNTDeviceName.Buffer, ucNTDeviceName.Length + sizeof( WCHAR ),
					  NULL,0 );

		goto EXIT;
	}

	// initialize device and extension
	pDevObj->Flags |= DO_BUFFERED_IO;

	pDevExt = ( PSOH_DEV_EXT ) pDevObj->DeviceExtension;

	pDevExt->Type = SOH_OBJECT_TYPE_DEV_EXT;
	pDevExt->ucName.Buffer = pDevExt->aName;
	pDevExt->ucName.MaximumLength = sizeof( pDevExt->aName );

	RtlCopyUnicodeString( &pDevExt->ucName, &ucRegDeviceName );

	pDevExt->pDeviceObject = pDevObj;

	// create controlling streams
	PortCount = DigiSohInitializeMiniports( pDevExt, &ucParametersPath );

	if (PortCount == 0) 
	{
		// error processing
		DigiDump( DIGIERRORS, ( "SOH: No Miniports %wZ \n", &ucNTDeviceName ) );
		DigiLogError( pDevObj,
					  SOH_NO_MINIPORTS,
					  5,
					  NULL,0,
					  ucNTDeviceName.Buffer, ucNTDeviceName.Length + sizeof( WCHAR ),
					  NULL,0 );

		goto EXIT;

	}

#endif

	// Get the Max Streams per port from the registry if it is there.
	(void)DigiRegQueryDword( hKeyDriverParameters,
							 DIGISOH_REGVAL_MAX_STREAMS_PER_PORT,
							 &gulMaxStreams,
							 DigiRegFlagOptional,
							 DIGISOH_REGVAL_MAX_STREAMS_PER_PORT_DEFAULT );

	// error processing
	DigiDump( DIGIINIT,( "SOH: MaxStreams = %d, PortCount = %d\n", gulMaxStreams, PortCount ) );

	// Multiply by the Ports Installed
	gulMaxStreams *= PortCount;

	// Initialize the Streams RuntimeEnvironment.  This must be called
	// before NT4 systems build the protocol stacks.  
	Status = InitStreamsRuntimeEnv( pDriverObject,
									gulMaxStreams,
									hKeyDriver,
									hKeyDriverParameters,
									PortCount);

#if !NT5_BUILD

	// We can override the building of the protocol stacks.  This
	// is used for test purposes
	(void)DigiRegQueryDword( hKeyDriver,
							 L"BuildProtocolStacks",
							 &fBuildProtocolStacks,
							 DigiRegFlagOptional,
							 TRUE );

	if (fBuildProtocolStacks == TRUE) {

		// create controlling streams
		Status = DigiSohCreateControllingStreams( pDevObj,
												  pDevExt,
												  &ucParametersPath );

		if ( !NT_SUCCESS( Status ) )
		{

			// error handling
			DigiDump( DIGIERRORS, ( "SOH: Couldn't create the controlling streams.\n" ) );

			DigiLogError( pDevObj,
						  SOH_CONTROLLING_STREAMS_ERROR,
						  14,
						  NULL,0,
						  NULL,0,
						  NULL,0 );

			goto EXIT;
		}

	}

	// Create symbolic link to NT namespace name for this device to allow
	// win32 apps access // (e.g. \DosDevices\DigiSoh)
	Status = DigiStrSymbolicName( &ucDosDeviceName, ucRegDeviceName.Buffer );

	if ( !NT_SUCCESS( Status ) )
	{
		// error handling
		DigiDump( DIGIERRORS, ( "SOH: Couldn't get space for the symbolic name.\n" ) );

		DigiLogError( pDevObj,
					  SOH_INSUFFICIENT_RESOURCES,
					  15,
					  NULL,0,
					  NULL,0,
					  NULL,0 );

		goto EXIT;
	}

	Status = IoCreateSymbolicLink( &ucDosDeviceName, &ucNTDeviceName );

	if ( !NT_SUCCESS( Status ) )
	{

		// error processing
		DigiDump( DIGIERRORS, ( "DigiSoh: Couldn't create the symbolic link\n"
							"-------  from %wZ to %wZ\n",
							&ucDosDeviceName,
							&ucNTDeviceName ) );

		DigiLogError( pDevObj,
					  SOH_NO_SYMLINK_CREATED,
					  16,
					  NULL,0,
					  ucNTDeviceName.Buffer,ucNTDeviceName.Length + sizeof( WCHAR ),
					  ucDosDeviceName.Buffer, ucDosDeviceName.Length + sizeof( WCHAR ) );

		goto EXIT;
	}

	RtlInitUnicodeString( &pDevExt->ucSymbolicLinkName, NULL );
	Status = DigiStrAllocUnicodeString( &pDevExt->ucSymbolicLinkName,
								        ucDosDeviceName.Buffer,
								        NULL,
										NULL);

	if ( !NT_SUCCESS( Status ) )
	{
		// error handling
		DigiDump( DIGIERRORS, ( "SOH: Couldn't get space for the symbolic name.\n" ) );

		DigiLogError( pDevObj,
					  SOH_INSUFFICIENT_RESOURCES,
					  17,
					  NULL,0,
					  NULL,0,
					  NULL,0 );

		goto EXIT;
	}

#endif

EXIT:

	if ( !NT_SUCCESS( Status ) )
	{

		ShutdownStreamsRuntimeEnv(TRUE);

#if !NT5_BUILD

		if (pDevObj)
		{

			// Deregister the Miniport Devices
			XwanDeregisterMiniports();

			if ( pDevExt )
			{
				if ( pDevExt->ucSymbolicLinkName.Buffer )
					DigiStrFreeUnicodeString( &pDevExt->ucSymbolicLinkName );
			}

			IoDeleteDevice( pDevObj );

		}

#endif

	}

	if ( ucDosDeviceName.Buffer )
		DigiStrFreeUnicodeString( &ucDosDeviceName );

	if ( ucNTDeviceName.Buffer )
		DigiStrFreeUnicodeString( &ucNTDeviceName );

	if ( hKeyDriverParameters )
		DigiRegCloseKey( hKeyDriverParameters );

	if ( ucParametersPath.Buffer )
		DigiStrFreeUnicodeString( &ucParametersPath );

	if ( hKeyDriver )
		DigiRegCloseKey( hKeyDriver );

//	MemPrintQuit(  );

	DigiDump( DIGIINIT, ( "DigiSoh: Leaving DriverEntry, Status = %x\n",Status ) );
	return ( Status );
}								   // end DriverEntry



VOID
DigiSohUnload( IN PDRIVER_OBJECT pDriverObject )
/*++

Description:

Unloads the driver.

Arguments:

pDriverObject - Pointer to this driver object.

Return Value:

STATUS_SUCCESS

--*/
{
	PDEVICE_OBJECT  pDevObj;
	PSOH_DEV_EXT    pDevExt;

#if !NT5_BUILD
	pDevObj = pDriverObject->DeviceObject;
	pDevExt = pDevObj->DeviceExtension;
#endif

	DigiDump( DIGIINIT, ( "DigiSoh: Entering DigiSohUnload\n" ) );

	// bring the driver down - see DDK, chapter 15

	// Sutdown the threads and release resources
	ShutdownStreamsRuntimeEnv(TRUE);

#if !NT5_BUILD
	// Deregister the Miniport Devices
	XwanDeregisterMiniports();
#endif

	DigiFreeMem( gapStreamObj );

#if !NT5_BUILD
	// clean up device extension and delete device
	IoDeleteSymbolicLink( &pDevExt->ucSymbolicLinkName );
	DigiStrFreeUnicodeString( &pDevExt->ucSymbolicLinkName );

	// CloseThe DigiSoh Device
	if (gSohRuntimeEnv.hDevice)
	{
		(void) DsCloseDevice( gSohRuntimeEnv.hDevice );
	}

	IoDeleteDevice( pDevObj );

	// shut down mem print
	MemPrintQuit(  );
#endif

	DigiDump( DIGIINIT, ( "DigiSoh: Exiting DigiSohUnload\n" ) );

	return;
}								   // end DigiSohUnload




NTSTATUS
DigiSohCreate( IN PDEVICE_OBJECT pDevObj,
			   IN PIRP pIrp )
/*++

Description:

Opens the device associated with DeviceObject. We always allow the device to
be opened.

Arguments:

pDevObj - Pointer to this devices object.

pIrp - Pointer to the create IRP request.

Return Value:

STATUS_SUCCESS

--*/
{
	DigiDump( DIGIFLOW, ( "DigiSoh: Entering DigiSohCreate\n" ) );

	pIrp->IoStatus.Information = 0L;
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );

	DigiDump( DIGIFLOW, ( "DigiSoh: Exiting DigiSohCreate\n" ) );

	return ( STATUS_SUCCESS );
}								   // end DigiSohCreate




NTSTATUS
DigiSohClose( IN PDEVICE_OBJECT pDevObj,
			  IN PIRP pIrp )
/*++

Description:

	Closes the device associated with DeviceObject. We will always close the
	device successfully.

Arguments:

	pDevObj - Pointer to this devices object.

	pIrp - Pointer to the close IRP request.


Return Value:

	STATUS_SUCCESS

--*/
{
	PSOH_DEV_EXT    pDevExt;
	KIRQL           DevKirql;
	ULONG           StrmObjId;
	PSOH_STREAM_OBJECT pStreamObj;
	DS_CLOSE        Close;
	PIRP			pTmpIrp;
	KEVENT			CloseEvent;
	IO_STATUS_BLOCK IoStatus;
	NTSTATUS		Status;

	DigiDump( DIGIFLOW, ( "DigiSoh: Entering DigiSohClose\n" ) );

	// complete this irp

	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );

	DigiDump( DIGIFLOW, ( "DigiSoh: Exiting DigiSohClose\n" ) );
	return ( STATUS_SUCCESS );
}								   // end DigiSohClose


NTSTATUS
DigiSohDeviceControl( IN PDEVICE_OBJECT pDevObj,
					  IN PIRP pIrp )
/*++

Description:

Supports the Digi STREAMS Client API via Windows NT IRP_MJ_DEVICE_CONTROL IRP
functions. Various API routines called based on the the contents of
pIrpStack->Parameters.DeviceIoControl.IoControlCode.

??? describe difference between use mode and kernel mode requests? (i.e. lock down buffers?)

Parameters:

pDevObj - A pointer to this device's object

pIrp - A pointer to this device's IRP

Return Values:

Always returns SUCCESS_PENDING

Comments:

This routine supports the following DSTREAMS functions:

        DsOpen, DsClose, DsIoctl, DsLink, DsPush, DsPutmsg, DsGetmsg and DsPurge

IRPs are decoupled from the DSTREAMS Subsystem by placing pending IRPs in the
Stream Object. The DSTREAMS Subsystem processes the IRPs, copying data between
IRP buffers and STREAMS buffers, and updates the Stream Object to indicate the
IRP is ready for completion. The IRP Completion thread then completes the IRPs
and removes them from the Stream Object.

-- */

{
	PIO_STACK_LOCATION pIrpStack;
	NTSTATUS Status = STATUS_UNSUCCESSFUL;

	DigiDump( DIGIFLOW, ( "Entering DigiSohDeviceControl: IRP = 0x%x, RequestorMode = 0x%x\t\n",
					  pIrp,
					  pIrp->RequestorMode ) );

	pIrpStack = IoGetCurrentIrpStackLocation( pIrp );

	switch ( pIrpStack->Parameters.DeviceIoControl.IoControlCode )
	{
	case IOCTL_DS_OPEN:

		Status = SohDsOpen( pDevObj, pIrp );

		break;

	case IOCTL_DS_CLOSE:

		Status = SohDsClose( pDevObj, pIrp );

		break;

	case IOCTL_DS_IOCTL:

		Status = SohDsIoctl( pDevObj, pIrp );

		break;

	case IOCTL_DS_PUSH:

		Status = SohDsPush( pDevObj, pIrp );

		break;

	case IOCTL_DS_LINK:

		Status = SohDsLink( pDevObj, pIrp );

		break;

	case IOCTL_DS_PUTMSG:

		Status = SohDsPutmsg( pDevObj, pIrp );

		break;

	case IOCTL_DS_GETMSG:

		Status = SohDsGetmsg( pDevObj, pIrp );

		break;

	case IOCTL_DS_PURGE:

		Status = SohDsPurge( pDevObj, pIrp );

		break;

	case IOCTL_KME_READ:

		Status = SohKmeRead( pDevObj, pIrp );
		break;

	case IOCTL_KME_WRITE:

		Status = SohKmeWrite( pDevObj, pIrp );
		break;

	case IOCTL_SOH_GET_STREAMS:

		Status = SohAllocGetStreams( pIrp );
		break;

	case IOCTL_SOH_GET_DATABLOCKS:

		Status = SohAllocGetDataBlocks( pIrp );
		break;

	default:

		Status = STATUS_INVALID_PARAMETER;
        pIrp->IoStatus.Information = 0;
		pIrp->IoStatus.Status = Status;

		IoCompleteRequest( pIrp, IO_NO_INCREMENT );

		break;
	}							   // end switch( IoControlCode )


	DigiDump( DIGIFLOW, ( "Exiting DigiSohDeviceControl: IRP = 0x%x\t\n",
					  pIrp ) );

	return ( Status );
}								   // end DigiSohDeviceControl



NTSTATUS
DigiSohCleanup( IN PDEVICE_OBJECT pDevObj,
				IN PIRP pIrp )
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PSOH_DEV_EXT    pDevExt;
	KIRQL           StrmKirql;
	KIRQL           DevKirql;
	ULONG           StrmObjId;
	PSOH_STREAM_OBJECT pStreamObj;
	DS_CLOSE		Close;
	PIRP			pTmpIrp;
	KEVENT			CloseEvent;
	IO_STATUS_BLOCK IoStatus;
	NTSTATUS		Status;

	DigiDump( DIGIFLOW, ( "DigiSoh: Entering DigiSohCleanup\n" ) );

	// To use IoBuildDeviceIoControlRequest we must be at passive level

	ASSERT( KeGetCurrentIrql(  ) == PASSIVE_LEVEL );

	pDevExt = pDevObj->DeviceExtension;

	// Protect the Global stream object 
	KeAcquireSpinLock( &gSohRuntimeEnv.SpinLock, &DevKirql );

	// for each stream associated with the OriginalFileObject close the stream
	// If it is already in the process of closing then wait for the close
	// to complete

	for ( StrmObjId = 1; StrmObjId < gulMaxStreams; StrmObjId++ )
	{

		// hold lock while examining object so it's not freed

		pStreamObj = gapStreamObj[StrmObjId];

		if ( ( pStreamObj != NULL ) &&
			 ( pStreamObj->pOriginalFileObject == pIrp->Tail.Overlay.OriginalFileObject )
		   )
		{

			KeReleaseSpinLock( &gSohRuntimeEnv.SpinLock, DevKirql );

			////// Start Processing the Stream

			DigiDump( DIGIINIT, ( "Cleanup:Stream %x\n",pStreamObj ) );
				
			KeAcquireSpinLock( &pStreamObj->SpinLock, &StrmKirql );

			// While the cancel is pending no Irps can be completed on this stream.
			pStreamObj->CancelState = SOH_PROCESS_STATE_CANCEL_PENDING;

			if (pStreamObj->ProcessState == SOH_PROCESS_STATE_DSTRM_RUNNING)
			{
				KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );	

				// Wait till the streams thread is ready
				KeWaitForSingleObject( &pStreamObj->evCleanup,
									   Executive,
									   KernelMode,
									   TRUE,
									   NULL );


			}
			else
			{
				KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );	
			}

			
			DigiDump( DIGIINIT, ( "Cleanup:Close Stream %x\n",pStreamObj ) );

			// There is one special case.  If a CloseIrp was in progress
			// then will complete it, and send another to cleanup things.
			if ( pStreamObj->pCloseIrp )
			{

				DigiDump( DIGIINIT, ( "Cleanup:Close In Prog Stream %x\n",pStreamObj ) );

				pStreamObj->pCloseIrp->IoStatus.Status = STATUS_CANCELLED;
				pStreamObj->pCloseIrp->IoStatus.Information = 0;
			}

			// send a DsClose IOCTL (via Irp) to the Stream
			// pStreamObj->hObjectId is the proper handle
			KeInitializeEvent( &CloseEvent, NotificationEvent, FALSE );
			RtlZeroMemory( &IoStatus, sizeof( IoStatus ) );
			RtlZeroMemory( &Close, sizeof( Close ) );
			Close.hStream = pStreamObj->hObjectId;

			pTmpIrp = IoBuildDeviceIoControlRequest( (ULONG) IOCTL_DS_CLOSE,
												  pStreamObj->pDevExt->pDeviceObject,
												  &Close,
												  sizeof( Close ),
												  NULL,
												  0,
												  FALSE,
												  &CloseEvent,
												  &IoStatus );

			// this should always succeed, but we want to know if it doesn't
			// unsuccessful purges will be handled in close

			ASSERT( pTmpIrp );

			if ( !pTmpIrp )
			{
				continue;
			}

			Status = IoCallDriver( pStreamObj->pDevExt->pDeviceObject, pTmpIrp );

			if ( Status == STATUS_PENDING )
			{

				// Allow the DsClose Irp to be Processed, the close should
				// always pend
				DigiDump( DIGIINIT, ( "Cleanup:Close Go Idle %x\n",pStreamObj ) );

				KeAcquireSpinLock( &pStreamObj->SpinLock, &StrmKirql );
				pStreamObj->CancelState = SOH_PROCESS_STATE_IDLE;
				KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );

				
				KeWaitForSingleObject( &CloseEvent, 
										Executive, 
										KernelMode, 
										FALSE, 
										NULL );

				Status = IoStatus.Status;
			}

			// this should always succeed, 

			ASSERT( Status == STATUS_SUCCESS );

			DigiDump( DIGIINIT, ( "Cleanup:Close Done %x\n",pStreamObj ) );

		}
		else
		{
			KeReleaseSpinLock( &gSohRuntimeEnv.SpinLock, DevKirql );
		}

		// Protect the Global stream object 
		KeAcquireSpinLock( &gSohRuntimeEnv.SpinLock, &DevKirql );

	}

	KeReleaseSpinLock( &gSohRuntimeEnv.SpinLock, DevKirql );

	// complete this irp

	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );

	DigiDump( DIGIFLOW, ( "DigiSoh: Exiting DigiSohCleanup\n" ) );
	return ( STATUS_SUCCESS );
}								   // end DigiSohCleanup


NTSTATUS
DigiSohDeviceShutdown(
	IN PDEVICE_OBJECT pDevice,
	IN PIRP           pIrp )
{
	PSOH_DEV_EXT    pDevExt = pDevice->DeviceExtension;
	ASSERT( pDevExt->Type == SOH_OBJECT_TYPE_DEV_EXT );
	DigiDump( DIGIINIT, ( "DigiSoh!DeviceShutdown: device %S\n", pDevExt->ucName.Buffer ) );

#if NT5_BUILD

	// ShutDown the Controlling Streams
	DigiSohCloseControllingStreams(pDevExt);

	// Sutdown the threads
   ShutdownStreamsRuntimeEnv(FALSE);

	// Stop the Adapter
	if (pDevExt->pXwanAdapter)
	{
		// Deregister the MiniPorts
		XwanDeregisterMiniports(pDevExt->pXwanAdapter);

		// Stop the Miniport Adapter
		XwanStopAdapter(pDevExt->pXwanAdapter);

		pDevExt->pXwanAdapter = NULL;
	}

#endif

	pIrp->IoStatus.Information = 0L;
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest( pIrp, IO_NO_INCREMENT );

	return STATUS_SUCCESS;
}

NTSTATUS
DigiSohInternalDeviceControl( IN PDEVICE_OBJECT DeviceObject,
							  IN PIRP Irp )
/*++

   Routine Description:

   This routine processes IOCTL requests.

   Arguments:

   DeviceObject - Pointer to this devices object.

   Irp - Pointer to the open IRP request.

   Return Value:


   -- */
{
	DigiDump( DIGIFLOW, ( "Entering DigiSohInternalIoControl\n" ) );

	Irp->IoStatus.Information = 0L;
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest( Irp, IO_NO_INCREMENT );

	DigiDump( DIGIFLOW, ( "Exiting DigiSohInternalIoControl\n" ) );

	return ( STATUS_SUCCESS );
}								   // end DigiSohInternalIoControl


//------------------------------------------------------------ MLZ 04/25/96 {
// Use this to test Event logging output - Needs Updating
#if 0


VOID
TestDriveEventLog( IN PDRIVER_OBJECT DriverObject,
				   IN PUNICODE_STRING RegistryPath )
{
	UNICODE_STRING  String1;
	UNICODE_STRING  String2;
	PHYSICAL_ADDRESS Zero = {0};

	RtlInitUnicodeString( &String1, L "MyString1" );
	RtlInitUnicodeString( &String2, L "MyString2" );

	DigiLogError( DriverObject,
				  SOH_NO_SYMLINK_CREATED,   // FinalStatus
				  18,	// SpecificIOStatus
				  NULL, 0,
				  String1.Buffer, String1.Length,	// Insert1 Addr, Len
				  String2.Buffer, String2.Length );	// Insert2 Addr, Len
}

#endif
//------------------------------------------------------------ MLZ 04/25/96 }



