/*++
*******************************************************************************
* Copyright (c) 1990-2001 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:

   DsSync.c

Abstract:

   This module is a kernelmode library for interfacing to Digi STREAMS service
   providers. It encapsulates NT and STREAMS-like system calls to relieve the client
   driver of managing these tasks. It is built as a library and linked with the client
   driver.

	This source file includes all APIs which complete synchronously, i.e. are guaranteed
	to complete (success or fail) before returning.

Author:

   Michael Zarns

--*/

#include <ntddk.h>

#include "DsApi.h"
#include "DsIoctl.h"
#include "digilib.h"
#include "DsDef.h"

///////////////////////////////////////////////////////////////////////////////
//
// Global Data
//

DSL_LIST gDeviceList;
DSL_LIST gStreamList;
BOOLEAN  gIsLibraryInitialized = FALSE;


///////////////////////////////////////////////////////////////////////////////
//
// Internal Routines
//
// All internal routines are named with a lowercase "ds" prefix.
// 

NTSTATUS
dsSyncIoComplete(
	IN PDEVICE_OBJECT pDeviceObject, 
	IN PIRP pIrp, 
	IN PVOID Context );


NTSTATUS
dsInitializeLibrary( 
	VOID )
/*++

Description:

	This routine initializes the library.  It must be called before any other library
	routine.

Return Value:

	STATUS_SUCCESS

--*/
{
	if ( gIsLibraryInitialized )
		return ( STATUS_SUCCESS );

	KeInitializeSpinLock( &gDeviceList.Lock );
	InitializeListHead( &gDeviceList.Head );

	KeInitializeSpinLock( &gStreamList.Lock );
	InitializeListHead( &gStreamList.Head );

	gIsLibraryInitialized = TRUE;
	return ( STATUS_SUCCESS );
}


INLINE NTSTATUS
dsSyncIoRequest(
	IN LONG           Ioctl,
	IN PDEVICE_OBJECT pDeviceObject,
	IN PVOID          pInputBuffer,
	IN ULONG          InputBufferLength,
	IN PVOID          pOutputBuffer,
	IN ULONG          OutputBufferLength )
/*++

Description:

	This routine performs a synchronous IoCallDriver request.  Doesn't call 
	IoBuildDeviceIoControlRequest, since that routine may only be called at 
	PASSIVE_LEVEL.  Build our own Irp and use dsSyncIoComplete to set event and 
	copy output data.

Parameters:

	Ioctl
		Specifies the IOCTL_DS_xxx to be called.

	pDeviceObject
		Points to the DeviceObject which is the target of this request.

	pInputBuffer
		Points to the input buffer to be passed into the request.

	InputBufferLength
		Length of input buffer

	pOutputBuffer
		Points to the output buffer in which the lower driver will return data.

	OutputBufferLength
		Length of output buffer

Return Value:

	STATUS_SUCCESS,
	STATUS_UNSUCCESSFUL,
	STATUS_INSUFFICIENT_RESOURCES

--*/
{
	IO_STATUS_BLOCK IoStatus;
	KEVENT Event;
	PIRP pIrp;
	PIO_STACK_LOCATION pIos;
	NTSTATUS Status;

	ASSERT( pDeviceObject );
	// If and only if buffer exists, length should be nonzero.
	ASSERT( ( pInputBuffer && InputBufferLength ) || 
	        ( !pInputBuffer && !InputBufferLength ) );
	ASSERT( ( pOutputBuffer && OutputBufferLength ) || 
	        ( !pOutputBuffer && !OutputBufferLength ) );
	ASSERT( OutputBufferLength <= InputBufferLength );

	KeInitializeEvent( &Event, NotificationEvent, FALSE );
	RtlZeroMemory( &IoStatus, sizeof( IoStatus ) );

	pIrp = IoAllocateIrp( pDeviceObject->StackSize, FALSE );
	if ( !pIrp )
		return( STATUS_INSUFFICIENT_RESOURCES );

	pIrp->RequestorMode = KernelMode;
	pIrp->AssociatedIrp.SystemBuffer = pInputBuffer;
	pIrp->UserBuffer = pOutputBuffer;
	pIrp->UserIosb = &IoStatus;
	pIrp->UserEvent = &Event;

	pIos = IoGetNextIrpStackLocation( pIrp );
	pIos->MajorFunction = IRP_MJ_DEVICE_CONTROL;
	pIos->Parameters.DeviceIoControl.IoControlCode = Ioctl;
	pIos->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
	pIos->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
	IoSetCompletionRoutine( pIrp, dsSyncIoComplete, NULL, TRUE, TRUE, TRUE );

 	Status = IoCallDriver( pDeviceObject, pIrp );
	if ( Status == STATUS_PENDING )
	{		
		//SDB: 
			
		LARGE_INTEGER Timeout;
		
		Timeout.QuadPart = (__int64)-1000 * 1000 *20;    // 2000ms

		Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, &Timeout );				
		
		// SDB: debug
		if ( Status == STATUS_TIMEOUT )
		{
			DbgPrint("***dsSyncIoRequest timed out\n");
		}

		Status = IoStatus.Status;
	}
	return( Status );
}


NTSTATUS
dsSyncIoComplete(
	IN PDEVICE_OBJECT pDeviceObject, 
	IN PIRP pIrp, 
	IN PVOID Context )
/*++

Description:

	CompletionRoutine for SyncIoRequest.  Copies output data to output buffer 
	and sets event.  

Parameters:

	pDeviceObject
		Device object passed into SyncIoRequest

	pIrp
		Irp being completed.

	Context
		Context passed into IoSetCompletionRoutine in SyncIoRequest.  Not used.

Return Value:

	STATUS_MORE_PROCESSING_REQUIRED

--*/
{
	if ( pIrp->UserBuffer && 
	     NT_SUCCESS( pIrp->IoStatus.Status ) && pIrp->IoStatus.Information )
	{
		RtlMoveMemory( pIrp->UserBuffer, 
		               pIrp->AssociatedIrp.SystemBuffer, 
		               pIrp->IoStatus.Information );
	}

	// Set return status
	ASSERT( pIrp->UserIosb );
	*pIrp->UserIosb = pIrp->IoStatus;

	// Set Completed event
	ASSERT( pIrp->UserEvent );
	KeSetEvent( pIrp->UserEvent, IO_NO_INCREMENT, FALSE );

	// Free resources
	IoFreeIrp( pIrp );

	return( STATUS_MORE_PROCESSING_REQUIRED );
}


///////////////////////////////////////////////////////////////////////////////
//
// The API Routines
//
// All API routines (exposed to the public, available for general use) are
// named with the mixedcase "Ds" prefix.
//

NTSTATUS
DsOpenDevice(
	IN PUNICODE_STRING pDeviceName,
	OUT PHANDLE        phDevice )
/*++

Description:

	This routine opens a connection to a DSTREAMS service provider for the named device.
	The returned handle is required as a parameter in subsequent library calls.

Parameters:

	pDeviceName
		Pointer to unicode string containing the name of the device to open, for example
		"\Device\DigiSxb".

	phDevice
		Pointer to receive handle required as a parameter in subsequent library calls.

Return Values:

	STATUS_SUCCESS if able to open connection to DSTREAMS service provider.
	STATUS_UNSUCCESSFUL
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER
	STATUS_OBJECT_TYPE_MISMATCH
	STATUS_OBJECT_NAME_INVALID
	STATUS_PRIVILEGE_NOT_HELD

--*/
{
	PDSL_DEVICE pDevice;
	UNICODE_STRING FullDeviceName;
	WCHAR FullDeviceNameString[64];
	NTSTATUS Status;
	KIRQL OldIrql;

	if ( !gIsLibraryInitialized )
		dsInitializeLibrary();

	// validate input parameters
	if ( !pDeviceName || !pDeviceName->Length || 
	     !pDeviceName->MaximumLength || !pDeviceName->Buffer )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( !phDevice )
		return( STATUS_INVALID_PARAMETER_2 );

	// allocate the wrapper
	pDevice = DigiAllocMem( NonPagedPool, sizeof( DSL_DEVICE ) );
	if ( !pDevice )
	  return ( STATUS_INSUFFICIENT_RESOURCES );

	// initialize the wrapper
	RtlZeroMemory( pDevice, sizeof( DSL_DEVICE ) );
	pDevice->Type = DSL_TYPE_DEVICE;
	pDevice->State = DSL_STATE_READY;

	// Open the device
	FullDeviceName.Buffer = FullDeviceNameString;
	FullDeviceName.MaximumLength = sizeof( FullDeviceNameString );
	FullDeviceName.Length = 0;
	RtlAppendUnicodeToString( &FullDeviceName, L"\\Device\\" );
	RtlAppendUnicodeStringToString( &FullDeviceName, pDeviceName );
	Status = IoGetDeviceObjectPointer( &FullDeviceName, FILE_ALL_ACCESS,
	                                   &pDevice->FileObject, &pDevice->DeviceObject );
	if ( !NT_SUCCESS( Status ) ) 
	{
		DigiFreeMem( pDevice );
#if DBG
		DbgPrint("*** Unable to access %wZ ***\n", pDeviceName );
#endif
		return ( Status );
	}

#if NT5_BUILD
	// Always delete this reference
	(void)ObDereferenceObject( pDevice->FileObject );
#endif

	// Add this device to our global list
	KeAcquireSpinLock( &gDeviceList.Lock, &OldIrql );
	InsertTailList( &gDeviceList.Head, &pDevice->ListEntry );
	KeReleaseSpinLock( &gDeviceList.Lock, OldIrql );

	// set the handle to the address of this wrapper
	*phDevice = pDevice;
	return ( STATUS_SUCCESS );
}


NTSTATUS
DsOpenDeviceInternal(
	IN PDEVICE_OBJECT pDeviceObject,
	OUT PHANDLE       phDevice )
/*++

Description:

	This routine opens a connection to a DSTREAMS service provider for the named device.
	The returned handle is required as a parameter in subsequent library calls.

	This routine should *ONLY* be called from a service provider to obtain a handle to
	one of it's own devices.  This would be done for init-time config and building 
	protocol stacks.

Parameters:

	pDeviceObject
		Pointer to device object

	phDevice
		Pointer to receive handle required as a parameter in subsequent library calls.

Return Values:

	STATUS_SUCCESS if able to open connection to DSTREAMS service provider.
	STATUS_UNSUCCESSFUL
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER
	STATUS_OBJECT_TYPE_MISMATCH
	STATUS_OBJECT_NAME_INVALID
	STATUS_PRIVILEGE_NOT_HELD

--*/
{
	PDSL_DEVICE pDevice;
	KIRQL OldIrql;

	if ( !gIsLibraryInitialized )
		dsInitializeLibrary();

	// validate input parameters
	if ( !pDeviceObject )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( !phDevice )
		return( STATUS_INVALID_PARAMETER_2 );

	// allocate the wrapper
	pDevice = DigiAllocMem( NonPagedPool, sizeof( DSL_DEVICE ) );
	if ( !pDevice )
	  return ( STATUS_INSUFFICIENT_RESOURCES );

	// initialize the wrapper
	RtlZeroMemory( pDevice, sizeof( DSL_DEVICE ) );
	pDevice->Type = DSL_TYPE_DEVICE;
	pDevice->State = DSL_STATE_READY;
	pDevice->bOpenedInternal = TRUE;
	pDevice->DeviceObject = pDeviceObject;

	// Add this device to our global list
	KeAcquireSpinLock( &gDeviceList.Lock, &OldIrql );
	InsertTailList( &gDeviceList.Head, &pDevice->ListEntry );
	KeReleaseSpinLock( &gDeviceList.Lock, OldIrql );

	// set the handle to the address of this wrapper
	*phDevice = pDevice;
	return ( STATUS_SUCCESS );
}


NTSTATUS
DsCloseDevice( 
	IN HANDLE hDevice )
/*++

Description:

	This routine closes a connection to a DSTREAMS service provider and
	invalidates the handle.  This routine does *NOT* verify that streams opened on this
	device have been closed!

	hDevice
		Handle returned from DsOpenDevice.

Return Values:

	STATUS_SUCCESS if able to close the device,
	STATUS_INVALID_PARAMETER,
	STATUS_UNSUCCESSFUL if streams still referencing this device.

-- */
{
	const PDSL_DEVICE pDevice = hDevice;
	KIRQL OldIrql;

	// validate input parameters
	if ( !pDevice || pDevice->Type != DSL_TYPE_DEVICE )
		return ( STATUS_INVALID_PARAMETER );
	if ( pDevice->State != DSL_STATE_READY )
		return( STATUS_DSAPI_DEVICESTATE );

	// Remove this Device from the global device list
	KeAcquireSpinLock( &gDeviceList.Lock, &OldIrql );
	RemoveEntryList( &pDevice->ListEntry );
	KeReleaseSpinLock( &gDeviceList.Lock, OldIrql );

	// Release acquired resources
	if ( !pDevice->bOpenedInternal )
	{
#if !NT5_BUILD
		// Dereferenced this already in OpenDevice
		ObDereferenceObject( pDevice->FileObject );
#endif
	}
	else
	{
		ASSERT( !pDevice->FileObject );
	}

	DigiFreeMem( pDevice );
	return( STATUS_SUCCESS );
}


NTSTATUS
DsOpen( 
	IN HANDLE   hDevice,
	IN PCSZ     pcszStreamDeviceName,
	IN HANDLE   hOwner,
	IN PVOID    pUserData OPTIONAL,
	IN ULONG    cbUserData,
	OUT PHANDLE phStream,
	OUT PULONG  pError )
/*++

Description:

	This routine opens a Stream via a DSTREAM API IOCTL_DS_OPEN request on
	behalf of the caller. The returned handle is required as a parameter in
	other library routines.

Parameters:

	hDevice
		Handle for the DSTREAMS service provider device returned from DsOpenDevice.
		The open request is sent to this device.

	pcszStreamDeviceName
		Pointer to a null terminated ASCII string containing the name of the STREAMS
		device to open, of the form "X250".

	hOwner
		Handle identifying the owner of this Stream. Unused by library. Passed back
		unaltered in callback routines for this Stream.

	pUserData
		Optional data to pass in firmware open request.  Size and usage is defined by the
		firmware entity being opened.

	cbUserData
		Count of bytes of user data to pass in firmware open request.

	phStream
		Pointer to receive handle required as a parameter in other library routines.

	pError
		Pointer to receive ErrorCode returned from protocol stack on failed open.
		Valid only when DsOpen returns STATUS_UNSUCCESSFUL.

Return Value:

	STATUS_SUCCESS
	STATUS_INVALID_PARAMETER_X where X identifies the invalid paramter
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_UNSUCCESSFUL

Comments:

	A Stream must be opened with DsOpen before calling DsClose, DsPutMsg,
	DsGetMsg, DsPush, DsLink, or DsIoctl. A Stream is closed by calling DsClose.
	The handle returned in phStream is used as a parameter to identify the Stream
	to operate on in these calls.

	The hOwner parameter is returned in the PutMsg and GetMsg completion
	routines. The client driver should set hOwner to something meaningful it
	can use in it's putmsg and getmsg completion routines. Typically, this
	would be some context such as pAdapter.

--*/
{
	const PDSL_DEVICE pDevice = hDevice;
	PDSL_STREAM pStream;
	NTSTATUS  Status;
	DS_OPEN Open;
	ULONG Length, i;

	// init output
	if ( pError )
		*pError = 0;
	if ( phStream )
		*phStream = 0;

	// validate input parameters
	if ( pDevice == NULL || pDevice->Type != DSL_TYPE_DEVICE )
		return ( STATUS_INVALID_PARAMETER_1 );
	if ( pcszStreamDeviceName == NULL )
		return ( STATUS_INVALID_PARAMETER_2 );
	if ( (pUserData == NULL) ^ (cbUserData == 0) )
		return STATUS_INVALID_PARAMETER_4;
	if ( phStream == NULL )
		return( STATUS_INVALID_PARAMETER_6 );
	if ( pError == NULL )
		return( STATUS_INVALID_PARAMETER_7 );

	if ( pDevice->State != DSL_STATE_READY )
		return( STATUS_DSAPI_DEVICESTATE );

	// Find length of input string
	for ( Length = 0; *(pcszStreamDeviceName+Length); Length++ )
		; // Do nothing
	if ( !Length || Length >= DS_DEVICENAME_SIZE )	
		return( STATUS_INVALID_PARAMETER_2 );

	// Alloc the stream object
	pStream = DigiAllocMem( NonPagedPool, sizeof( DSL_STREAM ) );
	if ( !pStream )
		return ( STATUS_INSUFFICIENT_RESOURCES );

	// Initialize stream object
	RtlZeroMemory( pStream, sizeof( DSL_STREAM ) );
	pStream->Type = DSL_TYPE_STREAM;
	pStream->pDevice = pDevice;
	pStream->hOwner = hOwner;
	KeInitializeSpinLock( &pStream->Lock );

	// Init the IOCTL_DS_OPEN request
	RtlZeroMemory( &Open, sizeof( Open ) );
	for ( i = 0; i < Length; i++ )
		Open.DeviceName[i] = *( pcszStreamDeviceName+i );

	Open.pProviderData = Open.pUserData = pUserData;
	Open.cbUserData    = cbUserData;
	Status = dsSyncIoRequest( IOCTL_DS_OPEN, pDevice->DeviceObject,
	                          &Open, sizeof( Open ),
	                          &Open, sizeof( Open ) );
	if ( NT_SUCCESS( Status ) ) 
	{
		// STATUS_SUCCESS means the request got to the streams engine.  Check
		// the ErrorCode to see whether the streams engine failed the request.
		if ( !Open.ErrorCode )
		{
			// Success!  Save the handle and add this StreamWrapper to the list.
			KIRQL OldIrql;
			pStream->hProviderStream = Open.hStream;
			pStream->State = DSL_STATE_READY;
			KeAcquireSpinLock( &gStreamList.Lock, &OldIrql );
			InsertTailList( &gStreamList.Head, &pStream->ListEntry );
			KeReleaseSpinLock( &gStreamList.Lock, OldIrql );
			*phStream = pStream;
		}
		else
		{
			// Streams engine rejected the open request.
			#if DBG
				DbgPrint("*** DS_OPEN failed, ErrorCode 0x%x ***\n", Open.ErrorCode );
			#endif
			*pError = Open.ErrorCode;
			Status = STATUS_UNSUCCESSFUL;
		}
	} 
#if DBG
	else
	{
		DbgPrint("*** DS_OPEN request failed, Status 0x%x ***\n", Status );
	}
#endif

	if ( !NT_SUCCESS( Status ) )
		DigiFreeMem( pStream );
	return ( Status );
}



NTSTATUS
DsClose(
	IN HANDLE hStream )
/*++

Description:

	This routine closes a Stream via a DSTREAM API IOCTL_DS_CLOSE request on
	behalf of the caller.

Parameters:

	hStream
		Handle received from DSOpen identifying the stream to close.

Return Values:

	STATUS_SUCCESS
	STATUS_INVALID_PARAMETER
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_TIMEOUT ???

--*/
{
	const PDSL_STREAM pStream = hStream;
	PDSL_DEVICE pDevice;
	NTSTATUS Status;
	DS_CLOSE Close;
	KIRQL OldIrql;

	// validate input parameters
	if ( !pStream )
		return ( STATUS_INVALID_PARAMETER );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, TRUE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	pDevice = pStream->pDevice;
	ASSERT( pDevice && 
	        pDevice->Type == DSL_TYPE_DEVICE && 
	        pDevice->State == DSL_STATE_READY );

	// Init the IOCTL_DS_CLOSE request
	RtlZeroMemory( &Close, sizeof( Close ) );
	Close.hStream = pStream->hProviderStream;

	Status = dsSyncIoRequest( IOCTL_DS_CLOSE, pDevice->DeviceObject,
	                          &Close, sizeof( Close ),
	                          NULL, 0 );

	if ( NT_SUCCESS( Status ) ) 
	{
		// Success, remove from list and free resources
		dsDereferenceStream( pStream, TRUE );
	}
	else
	{
		// Close Failed -- not likely to happen, but it could.
		DbgPrint( "*** DsClose failed (0x%x)\n",Status );
		
		KeAcquireSpinLock( &pStream->Lock, &OldIrql );
		pStream->State = DSL_STATE_READY;
		KeReleaseSpinLock( &pStream->Lock, OldIrql );
		dsDereferenceStream( pStream, FALSE );
	}
	return ( Status );
}



NTSTATUS
DsIoctl(
	IN HANDLE  hStream,
	IN ULONG   Command,
	IN PVOID   pData,
	IN ULONG   DataLength,
	OUT PULONG pErrorCode )
/*++

Description:

	This routine makes a DSTREAM API IOCTL_DS_IOCTL (I_STR ioctl) request on
	behalf of the caller.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream on which to send the ioctl.

	Ioctl
		The ioctl command to send to downstream modules and drivers.

	pData
		Pointer to the data field of the ioctl.

	DataLength
		Length in bytes of the data field.

	pErrorCode
		Pointer to receive error code returned by DSTREAMS service provider if this
		call returns STATUS_UNSUCCESSFUL.

Return Value:

	STATUS_SUCCESS
	STATUS_UNSUCCESSFUL, check pErrorCode
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_TIMEOUT ???
	STATUS_CANCELED ???

Comments:

	This command completes synchronously. Only one request should be active per
	stream at a time.  Streams may reject or ignore any ioctl, link or push requests
	while a Link request is pending.

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS Status;
	DS_IOCTL Ioctl;

	// Validate input parameters
	if ( !pStream )
		return ( STATUS_INVALID_PARAMETER_1 );
	if ( !pData )
		return ( STATUS_INVALID_PARAMETER_3 );
	if ( !DataLength )
		return( STATUS_INVALID_PARAMETER_4 );
	if ( !pErrorCode )
		return ( STATUS_INVALID_PARAMETER_5 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	*pErrorCode = 0;

	// Init the request
	RtlZeroMemory( &Ioctl, sizeof( Ioctl ) );
	Ioctl.hStream = pStream->hProviderStream;
	Ioctl.Command = Command;
	Ioctl.pDataIn = Ioctl.pDataOut = pData;
	Ioctl.cbDataIn = Ioctl.cbMaxDataOut = DataLength;

	Status = dsSyncIoRequest( IOCTL_DS_IOCTL, 
	                          pStream->pDevice->DeviceObject,
	                          &Ioctl, sizeof( Ioctl ),
	                          &Ioctl, sizeof( Ioctl ) );
	if ( NT_SUCCESS ( Status ) ) 
	{
		if ( Ioctl.ErrorCode )
		{
			// Error from streams engine, notify client.
			#if DBG
			DbgPrint( "*** DsIoctl failed, ReturnValue 0x%x, ErrorCode 0x%x ***\n",
			         Ioctl.ReturnValue, Ioctl.ErrorCode );
			#endif

			*pErrorCode = Ioctl.ErrorCode;
			Status = STATUS_UNSUCCESSFUL;
		}
	}
#if DBG
	else
	{
		DbgPrint( "*** DsIoctl failed, Status 0x%x ***\n", Status );
	}
#endif
	dsDereferenceStream( pStream, FALSE );
	return ( Status );
}



NTSTATUS
DsLink(
	IN HANDLE  hTopStream,
	IN HANDLE  hBottomStream,
	OUT PULONG pMuxId,
	OUT PULONG pErrorCode )
/*++

Description:

	This routine makes a DSTREAM API IOCTL_DS_LINK (link) request on
	behalf of the caller.

Parameters:

	hTopStream
		Handle returned from DsOpen identifying the Stream connected to the
		multiplexing driver.

	hBottomStream
		Handle returned from DsOpen identifying the Stream to be connected under the
		multiplexing driver.

	pMuxid
		Pointer to receive the muxid returned by DSTREAMS service provider if this
		call returned STATUS_SUCCESS.

	pErrorCode
		Pointer to receive error code returned by DSTREAMS service provider if this
		call returned STATUS_UNSUCCESSFUL.

Return Value:

	STATUS_SUCCESS
	STATUS_UNSUCCESSFUL, check pErrorCode, the muxid is not valid
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_TIMEOUT ???
	STATUS_CANCELED ???

Comments:

	This command completes synchronously. Only one request should be active per
	Stream at a time.  Streams may reject or ignore any ioctl, link or push requests
	while a Link request is pending.

--*/
{
	const PDSL_STREAM pUpper = hTopStream;
	const PDSL_STREAM pLower = hBottomStream;
	NTSTATUS Status;
	DS_LINK Link;

	// Sanity check input params
	if ( !pUpper )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( !pLower )
		return( STATUS_INVALID_PARAMETER_2 );

	if ( !NT_SUCCESS( dsReferenceStream( pUpper, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );
	if ( !NT_SUCCESS( dsReferenceStream( pLower, FALSE ) ) )
	{
		dsDereferenceStream( pUpper, FALSE );
		return( STATUS_DSAPI_STREAMSTATE );
	}

	ASSERT( pUpper->pDevice &&
	        pUpper->pDevice->Type == DSL_TYPE_DEVICE &&
	        pUpper->pDevice->State == DSL_STATE_READY );
	ASSERT( pLower->pDevice &&
	        pLower->pDevice->Type == DSL_TYPE_DEVICE &&
	        pLower->pDevice->State == DSL_STATE_READY );
	ASSERT( pUpper->pDevice == pLower->pDevice );

	// Init output 
	*pMuxId = 0;
	*pErrorCode = 0;

	// Init the request
	RtlZeroMemory( &Link, sizeof( Link ) );
	Link.hStreamUpper = pUpper->hProviderStream;
	Link.hStreamLower = pLower->hProviderStream;

	Status = dsSyncIoRequest( IOCTL_DS_LINK, 
	                          pUpper->pDevice->DeviceObject,
	                          &Link, sizeof( Link ),
	                          &Link, sizeof( Link ) );

	if ( NT_SUCCESS ( Status ) ) 
	{
		if ( Link.ErrorCode )
		{
			// Error from streams engine, notify client.
			DbgPrint( "*** DsLink failed, ErrorCode 0x%x ***\n", Link.ErrorCode );
			Status = STATUS_UNSUCCESSFUL;
			*pErrorCode = Link.ErrorCode;
		}
		*pMuxId = Link.MuxId;
	} 
	else
	{
		DbgPrint( "*** DsLink failed, Status 0x%x ***\n", Status );
	}
	dsDereferenceStream( pUpper, FALSE );
	dsDereferenceStream( pLower, FALSE );
	return ( Status );
}



NTSTATUS
DsPush(
	IN HANDLE  hStream,
	IN PCSZ    pcszModuleName,
	OUT PULONG pErrorCode )
/*++

Description:

	This routine makes a DSTREAM API IOCTL_DS_PUSH (push) request on
	behalf of the caller.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream connected to the
		multiplexing driver.

	pcszModuleName
		Pointer to a null terminated ASCII string containing the name of the STREAMS
		module to open, of the form "X250".

	pErrorCode
		Pointer to receive error code returned by DSTREAMS service provider if this
		call returned STATUS_UNSUCCESSFUL.

Return Value:

	STATUS_SUCCESS
	STATUS_UNSUCCESSFUL, check pErrorCode
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_TIMEOUT ???
	STATUS_CANCELED ???

Comments:

	This command completes synchronously. Only one request should be active per
	Stream at a time.  Streams may reject or ignore any ioctl, link or push requests
	while a Link request is pending.

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS Status;
	DS_PUSH Push;
	ULONG i, Length;
	
	// Validate input params
	if ( !pStream )
		return ( STATUS_INVALID_PARAMETER_1 );
	if ( !pcszModuleName )
		return ( STATUS_INVALID_PARAMETER_2 );
	if ( !pErrorCode )
		return ( STATUS_INVALID_PARAMETER_3 );

	// Find length of input string
	for ( Length = 0; *(pcszModuleName+Length); Length++ )
		; // Do nothing
	if ( !Length || Length >= DS_DEVICENAME_SIZE )	
		return( STATUS_INVALID_PARAMETER_2 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	// Init output 
	*pErrorCode = 0;

	// Init the request 
	RtlZeroMemory( &Push, sizeof( Push ) );
	Push.hStream = pStream->hProviderStream;

	for ( i = 0; i < Length; i++ )
		Push.ModuleName[i] = *( pcszModuleName+i );

	Status = dsSyncIoRequest( IOCTL_DS_PUSH, 
	                          pStream->pDevice->DeviceObject,
	                          &Push, sizeof( Push ),
	                          &Push, sizeof( Push ) );

	if ( NT_SUCCESS ( Status ) ) 
	{
		if ( Push.ErrorCode )
		{
			// Error from streams engine, notify client.
			DbgPrint( "*** DsPush failed, ReturnValue 0x%x, ErrorCode 0x%x ***\n",
			         Push.ReturnValue, Push.ErrorCode );
		  Status = STATUS_UNSUCCESSFUL;
		}
		*pErrorCode = Push.ErrorCode;
	} 
#if DBG
	else
	{
		DbgPrint( "*** DsPush failed, Status 0x%x ***\n", Status );
	}
#endif

	dsDereferenceStream( pStream, FALSE );
	return ( Status );
}



NTSTATUS
DsPurge( 
	IN HANDLE hStream, 
	IN ULONG  PurgeFlags )
/*++

Description:

	This routine makes an DSTREAM API IOCTL_DS_PURGE request on behalf of
	the caller. This causes queued IRPs to be completed immediately or canceled.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream to purge.

	PurgeFlags
		Flags (DS_PURGE_xxx) determining which queues to purge.

Return Value:

	STATUS_SUCCESS, or appropriate error.
	STATUS_INVALID_PARAMETER_1
	STATUS_INVALID_PARAMETER_2

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS Status;
	DS_PURGE Purge;

	// Validate parameters
	if ( !pStream )
		return ( STATUS_INVALID_PARAMETER_1 );
	if ( !PurgeFlags )
		return( STATUS_INVALID_PARAMETER_2 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	RtlZeroMemory( &Purge, sizeof( Purge ) );
	Purge.hStream = pStream->hProviderStream;
	Purge.Flags = PurgeFlags;

	Status = dsSyncIoRequest( IOCTL_DS_PURGE, 
	                          pStream->pDevice->DeviceObject,
	                          &Purge, sizeof( Purge ),
	                          NULL, 0 );

	if ( !NT_SUCCESS( Status ) ) 
	{
		DbgPrint("*** DsPurge: IOCTL_DS_PURGE Irp failed, Status 0x%x ***\n",Status );
	}
	dsDereferenceStream( pStream, FALSE );
	return ( Status );
}



NTSTATUS
DsPutMsg(
	IN HANDLE      hStream,
	IN PUCHAR      pControlBuffer,
	IN ULONG       ControlLength,
	IN PUCHAR      pDataBuffer OPTIONAL,	// Optional
	IN ULONG       DataLength OPTIONAL,    // Optional.  Must be zero if pDataBuffer is NULL.
	IN DS_PRIORITY Priority )	
/*++

Description:

	This routine makes a synchronous DSTREAM API IOCTL_DS_PUTMSG request on behalf of
	the caller and waits for completion before returning.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream on which to send the
		message.

	pControlBuffer
		Pointer to the control buffer. This buffer is owned by the library until this
		call returns.  The buffer *MUST* be allocated from NonPagedPool!

	ControlLength
		Length of control buffer.

	pDataBuffer
		Pointer to the data buffer. This buffer is owned by the library until this
		call returns.  The buffer *MUST* be allocated from NonPagedPool!

	DataLength
		Length of data buffer.

	Priority
		One of DS_PRIORITY_XXX values.  Specifying high or low priority makes the message
		a M_PCPROTO.  DS_PRIORITY_HIGH only valid if pControl and ControlLength are non-zero.

Return Value:

	STATUS_SUCCESS
	STATUS_UNSUCCESSFUL
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER_x
	STATUS_CANCELED - purged
	STATUS_XXX

Comments:

	This command completes synchronously.

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS  Status;
	DS_PUTMSG PutMsg;

	// Validate input parameters
	if ( !pStream || pStream->Type != DSL_TYPE_STREAM )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( ( pControlBuffer == NULL ) ^ ( ControlLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_2 );
	if ( ( pDataBuffer == NULL ) ^ ( DataLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_4 );
	if ( pControlBuffer == 0 && Priority != DS_PRIORITY_LOW )
		return STATUS_INVALID_PARAMETER_6;

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	RtlZeroMemory( &PutMsg, sizeof( PutMsg ) );
	PutMsg.hStream   = pStream->hProviderStream;
	PutMsg.pControl  = pControlBuffer;
	PutMsg.cbControl = ControlLength;
	PutMsg.pData     = pDataBuffer;
	PutMsg.cbData    = DataLength;

	// Priority should be zero for all M_DATA (data only) messages 
	ASSERT( ControlLength != 0 || Priority == 0 );
	PutMsg.Priority = Priority;

	Status = dsSyncIoRequest( IOCTL_DS_PUTMSG, 
	                          pStream->pDevice->DeviceObject,
	                          &PutMsg, sizeof( PutMsg ),
	                          NULL, 0 );
	dsDereferenceStream( pStream, FALSE );
	return ( Status );
}


NTSTATUS
DsGetMsg(
	IN HANDLE hStream,
	IN PUCHAR pControlBuffer,
	IN ULONG  ControlMaxLength,
	OUT PULONG pControlLength,
	IN PUCHAR pDataBuffer,
	IN ULONG  DataMaxLength,
	OUT PULONG pDataLength,
	IN DS_PRIORITY Priority )
/*++

Description:

	This routine makes a synchronous DStreams API IOCTL_DS_GETMSG request on behalf of
	the caller and waits for completion before returning.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream on which to send the
		message.

	pControlBuffer
		Pointer to the control buffer. This buffer is owned by the library until this
		call returns.  The buffer *MUST* be allocated from NonPagedPool!

	ControlMaxLength
		Length of control buffer.

	ControlBytes
		Pointer to return value indicating the number of bytes written to the 
		Control buffer.

	pDataBuffer
		Pointer to the data buffer. This buffer is owned by the library until this
		call returns.  The buffer *MUST* be allocated from NonPagedPool!

	DataMaxLength
		Length of data buffer.

	DataLength
		Pointer to return value indicating the number of bytes written to the 
		Data buffer.

	Priority
		Minimum DS_PRIORITY level of incoming message which may satisfy this request.

Return Value:

	STATUS_SUCCESS
	STATUS_UNSUCCESSFUL
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER_x
	STATUS_CANCELED - purged
	STATUS_XXX

Comments:

	This command completes synchronously.

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS  Status;
	DS_GETMSG GetMsg;

	// Validate input parameters
	// Validate input parameters
	if ( !pStream || pStream->Type != DSL_TYPE_STREAM )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( (pControlBuffer == NULL) ^ (ControlMaxLength == 0) )
		return( STATUS_INVALID_PARAMETER_2 );
	if ( pControlBuffer != NULL && pControlLength == NULL )
		return( STATUS_INVALID_PARAMETER_4 );
	if ( ( pDataBuffer == NULL ) ^ ( DataMaxLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_5 );
	if ( pControlBuffer == NULL && Priority != DS_PRIORITY_LOW )
		return( STATUS_INVALID_PARAMETER_6 );
	if ( Priority > DS_PRIORITY_MAX )
		return( STATUS_INVALID_PARAMETER_6 );
	if ( pDataBuffer == NULL && pDataLength == NULL )
		return( STATUS_INVALID_PARAMETER_7 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	RtlZeroMemory( &GetMsg, sizeof( GetMsg ) );
	GetMsg.hStream      = pStream->hProviderStream;
	GetMsg.pControl     = pControlBuffer;
	GetMsg.cbMaxControl = ControlMaxLength;
	GetMsg.pData        = pDataBuffer;
	GetMsg.cbMaxData    = DataMaxLength;
	GetMsg.Priority     = Priority;

	Status = dsSyncIoRequest( IOCTL_DS_GETMSG, 
	                          pStream->pDevice->DeviceObject,
	                          &GetMsg, sizeof( GetMsg ),
	                          &GetMsg, sizeof( GetMsg ) );
	dsDereferenceStream( pStream, FALSE );
	if ( NT_SUCCESS( Status ) )
	{
		if ( pControlLength != NULL )
			*pControlLength = GetMsg.cbControl;
		if ( pDataLength != NULL )
			*pDataLength = GetMsg.cbData;
	}
	return ( Status );
}
