/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

	rcautils.c

Abstract:

	Utility routines called by entry point functions. Split out into
	a separate file to keep the "entry point" files clean.

Revision History:

	Who         When        What
	--------    --------    ----------------------------------------------
	rmachin     2-18-97     Created (from pxutils)
	DChen       3-16-98     Bug fixing and cleanup
	JameelH     4-18-98     Cleanup
    
Notes:

--*/

#include "precomp.h"
#include "atm.h"
#include "stdio.h"

#define MODULE_NUMBER	MODULE_UTIL
#define _FILENUMBER		'LITU'


NTSTATUS
RCAIoComplete(
	      IN PDEVICE_OBJECT		DeviceObject,
	      IN PIRP			Irp,
	      IN PVOID			Context
	     )
/*++

Routine Description:
	Callback function for KsStreamIo() - invoked when stream write is complete. 
	Here we just free any packets we allocated, return any NDIS packets back to NDIS,
	and return the stream header to the global pool.
	
Arguments:
	DeviceObject - Our Device Object
	Irp	     - The IRP that completed
	Context	     - A pointer to the stream header
	
Return Value:
	STATUS_SUCCESS	
	   
--*/
{
	PRCA_STREAM_HEADER	StreamHeader;
	PNDIS_BUFFER		pNdisBuffer = 0;

	RCADEBUGP(RCA_INFO, ("RCAIoComplete(): enter, context == %x\n", Context));

	StreamHeader = (PRCA_STREAM_HEADER)Context;

	RCACoNdisReturnPacket(StreamHeader->NdisPacket);

	RCASHPoolReturn(StreamHeader);	

	RCADEBUGP(RCA_INFO, ("RCAIoComplete(): exit"));

	return STATUS_SUCCESS;
}

VOID 
CopyStreamHeaderToIrp(
		      IN PRCA_STREAM_HEADER	NetRCAStreamHeader,
		      IN PIRP			Irp
		      ) 
{
	PIO_STACK_LOCATION		IrpStack;
	ULONG				NetBufferLength, BufferLength;
	PBYTE				NetBuffer, Buffer;
	PKSSTREAM_HEADER		NetStreamHdr, StreamHdr;
	PMDL				NetMdl, Mdl;
	ULONG				BytesLeft;
	ULONG				BytesFree;
	ULONG				BytesToCopy;
	ULONG				BytesRead = 0;

	NetStreamHdr = &NetRCAStreamHeader->Header;
	NetBuffer = NetStreamHdr->Data;
	BytesLeft = NetStreamHdr->DataUsed;

	RCADEBUGP(RCA_INFO, ("CopyStreamHeaderToIrp(): Going to copy %lu bytes\n", BytesLeft));
	// read IRP
	IrpStack = IoGetCurrentIrpStackLocation(Irp);

	BufferLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
	StreamHdr = (PKSSTREAM_HEADER)Irp->AssociatedIrp.SystemBuffer;
	StreamHdr->DataUsed = 0;
	//StreamHdr->PresentationTime.Time = StreamHdr->PresentationTime.Time;

	Mdl = Irp->MdlAddress;
	Buffer = MmGetSystemAddressForMdl(Mdl);
	Mdl = Mdl->Next;
    
	//
	// Enumerate the stream headers, filling in each one.
	// Assume the net IRP has one stream header
	//
	while (BytesLeft) 
	{
		BytesFree = StreamHdr->FrameExtent - StreamHdr->DataUsed;
		if(BytesFree)
		{
			BytesToCopy = BytesFree < BytesLeft ? BytesFree : BytesLeft;
			BytesLeft -= BytesToCopy;

			RtlCopyMemory(Buffer+StreamHdr->DataUsed,
				      NetBuffer,
				      BytesToCopy);

			BytesRead += BytesToCopy;
			StreamHdr->DataUsed += BytesToCopy;
			BytesFree = StreamHdr->FrameExtent - StreamHdr->DataUsed;
			
			NetBuffer += BytesToCopy;
		}

		// read stream full?
		if (!BytesFree)
		{
			//StreamHdr->PresentationTime.Numerator = 1;
			//StreamHdr->PresentationTime.Denominator = 1;
			//StreamHdr->Duration = StreamHdr->DataUsed; 
			//StreamHdr->OptionsFlags = KSSTREAM_HEADER_OPTIONSF_TIMEVALID | KSSTREAM_HEADER_OPTIONSF_DURATIONVALID;

			// get the next stream header
			BufferLength -= sizeof(KSSTREAM_HEADER);
			if (BufferLength)
			{
				StreamHdr++;
				StreamHdr->DataUsed = 0;
				//StreamHdr->PresentationTime.Time = StreamHdr->PresentationTime.Time + BytesRead;

				if(StreamHdr->FrameExtent)
				{
					if(Mdl)
					{
						Buffer = (PUCHAR) MmGetSystemAddressForMdl(Mdl);
						RCAAssert(Buffer);
						Mdl = Mdl->Next;
					}
					else
					{
						break;
					}
				}
			} 
			else
			{
				break;
			}
		}
	}

#if DBG
	if(BytesLeft)
	{
		RCADEBUGP(RCA_ERROR,("CopyIrpData: OOPS - There are bytes left over: BytesLeft = %d BytesRead = %d \n", 
			BytesLeft, BytesRead));
		//RCAAssert(FALSE);
	}
#endif

	// free the IRP's
	RCAIoComplete(NULL, NULL, (PVOID)NetRCAStreamHeader);

	Irp->IoStatus.Information = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
	Irp->IoStatus.Status = STATUS_SUCCESS;

	IoCompleteRequest(Irp, IO_NO_INCREMENT);

}

VOID
RCAIoWorker(
	    IN PNDIS_WORK_ITEM pNdisWorkItem,
	    IN PVOID	Context
	   )
/*++

Routine Description:
	This is the work item for the capture filter bridge pins. It
	streams the data in the work queue associated with a pin
	instance to the next connected filter.
	
Arguments:
	 PVOID Context - pointer to bridge pin instance.
	 
Return:
	 Nothing.

Comments:
	 Not pageable, uses SpinLocks.

--*/
{
	NTSTATUS				Status;
	PLIST_ENTRY				pList;
	PWORK_ITEM				pWorkItem;   
	PPIN_INSTANCE_BRIDGE	PinInstance = (PPIN_INSTANCE_BRIDGE)Context;
	PRCA_STREAM_HEADER		StreamHeader;

#if AUDIO_SINK_FLAG
	PIRP				Irp;
	PPIN_INSTANCE_DEVIO		DevIoPin;
#endif

	RCADEBUGP(RCA_INFO, ("RCAIoWorker(): enter\n"));

	RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);

	while(!IsListEmpty(&PinInstance->WorkQueue))
	{
		RcaGlobal.QueueSize--;
		RCADEBUGP(RCA_LOUD,("RCAIoWorker: Queue-- is %d\n", RcaGlobal.QueueSize));

		pList = RemoveHeadList(&PinInstance->WorkQueue);

		pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, ListEntry);

		RCA_RELEASE_BRIDGE_PIN_LOCK(PinInstance);
	       
		StreamHeader = pWorkItem->StreamHeader;

#if AUDIO_SINK_FLAG
		DevIoPin = PinInstance->FilterInstance->DevIoPin;
		
		//
		// The DevIo pin could have gone away between when we queued this packet and now. 
		//

		if (DevIoPin == NULL) {
			RCAIoComplete(NULL, NULL, (PVOID)StreamHeader);
			
			//
			// Have to continue instead of breaking because we have to IoComplete everything
			// in the queue.
			//
			RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);
			continue; 
		}	

		if (DevIoPin->ConnectedAsSink) {
		
			if (IsListEmpty(&DevIoPin->ActiveQueue)) {
				//
				// No IRP waiting for data, so just dump it. 
				//

				RCAIoComplete(NULL, NULL, (PVOID)StreamHeader);
				RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);
				continue;
			}

			Irp = KsRemoveIrpFromCancelableQueue(&DevIoPin->ActiveQueue,
							     &DevIoPin->QueueLock,
							     KsListEntryHead,
							     KsAcquireAndRemove);
			if (Irp == NULL) {	
				//
				// No IRP waiting for data, so just dump it. 
				//

				RCAIoComplete(NULL, NULL, (PVOID)StreamHeader);
				RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);
				continue;
			}	

			CopyStreamHeaderToIrp(StreamHeader, Irp);
			Status = NDIS_STATUS_SUCCESS;
		} else {
#endif
			
			if (PinInstance->FilterInstance->NextFileObject == (PFILE_OBJECT)NULL) {
				RCADEBUGP(RCA_WARNING, ("RCAIoWorker(): NextFileObject is NULL\n")); 
				// FIXME: Calling RCAIoComplete() with two null args is OK for now since we don't use
				//        those args anyway. But this is bad coding because it will break if we ever 
				//        change RCAIoComplete() to use them. Fix by abstracting out the functionality
				//        we want from RCAIoComplete into another function (which we can then call from
				//        RCAIoComplete).
				RCAIoComplete(NULL, NULL, (PVOID)StreamHeader);

				//
				// FIXME: Leak: nothing ever completes the IRP here.
				//

				RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);
				continue;
			}
		
			ASSERT(PinInstance->FilterInstance->NextFileObject);
			  
			Status = KsStreamIo(PinInstance->FilterInstance->NextFileObject,
					    NULL,
					    NULL,
					    RCAIoComplete,
					    (PVOID)StreamHeader,
					    KsInvokeOnSuccess | KsInvokeOnError | KsInvokeOnCancel,
					    &RcaGlobal.SHPool.IoStatus,
					    (PVOID)&StreamHeader->Header,
					    StreamHeader->Header.Size,
					    KSSTREAM_WRITE,
					    KernelMode);

			if (!((Status == STATUS_SUCCESS) || (Status == STATUS_PENDING))) {				
				RCADEBUGP(RCA_ERROR, ("KsStreamIo failed with Status == %x\n", Status));
			}


	

#if AUDIO_SINK_FLAG
		}
#endif

		RCA_ACQUIRE_BRIDGE_PIN_LOCK(PinInstance);
	
	}

	// Ok to schedule another work item now.
	PinInstance->bWorkItemQueued = FALSE;

	if (PinInstance->SignalMe) {
		RCADEBUGP(RCA_INFO, ("RCAIoWorker(): Unblocking PinDispatchClose()\n"));
		RCASignal(&PinInstance->Block, Status);
	}

	RCA_RELEASE_BRIDGE_PIN_LOCK(PinInstance);

	RCADEBUGP(RCA_INFO, ("RCAIoWorker(): exit\n"));

}


VOID
RCASHPoolInit(
	       VOID
	      )
/*++

Routine Description:
	Initializes the global stream header pool from which all RCA filters will obtain stream
	headers.
	      
Arguments:
	(None)

Return Value:
	(None)

--*/

{
	RCADEBUGP(RCA_INFO, ("RCASHPoolInit(): enter\n"));

	ExInitializeNPagedLookasideList(&RcaGlobal.SHPool.LookAsideList,
					NULL,
					NULL,
					0,
					sizeof(RCA_STREAM_HEADER),
                                        RCA_TAG, 
					(PAGE_SIZE / sizeof(RCA_STREAM_HEADER)));
	RcaGlobal.SHPool.FailCount = 0;

	RCADEBUGP(RCA_INFO, ("RCASHPoolInit(): exit\n"));
}



PRCA_STREAM_HEADER
RCASHPoolGet(
	      VOID
	     )
/*++                  
		      
Routine Description:  
	Obtains an stream header from the global pool. 
		      
Arguments:            
	(None)	      		      		      
		      
Return Value:         
	A pointer to the stream header, or NULL if no stream header could be obtained. 
		      
		      
--*/           
{
	PRCA_STREAM_HEADER	StreamHeader;

    RCADEBUGP(RCA_INFO, ("RCASHPoolGet(): enter\n"));
		
	StreamHeader = (PRCA_STREAM_HEADER)(ExAllocateFromNPagedLookasideList(&RcaGlobal.SHPool.LookAsideList));
	
	if (StreamHeader == NULL) {
		InterlockedIncrement(&RcaGlobal.SHPool.FailCount);
	}

        RCADEBUGP(RCA_INFO, ("RCASHPoolGet(): exit\n"));
	
	return StreamHeader;
}


VOID                      
RCASHPoolReturn(
		 IN PRCA_STREAM_HEADER StreamHeader
		)
/*++                 
		     
Routine Description: 
	Returns a stream header to the global pool. The stream header will be recycled for use later.
			     
Arguments:            	     
	StreamHeader - Pointer to the stream header being returned
		     
Return Value:        
	(None)	     
		     
--*/                 

{
    RCADEBUGP(RCA_INFO, ("RCASHPoolReturn(): enter\n"));


	ExFreeToNPagedLookasideList(&RcaGlobal.SHPool.LookAsideList,
				    (PVOID)StreamHeader);

	RCADEBUGP(RCA_INFO, ("RCASHPoolReturn(): exit\n"));
}


VOID            
RCASHPoolFree(
	       VOID
	      )
/*++
Routine Description:
	Frees any stream headers in the global IRP pool.
	
Arguments:
	(None)
	
Return Value:
	(None)
--*/
{               
    RCADEBUGP(RCA_INFO, ("RCASHPoolFree(): enter\n"));

	ExDeleteNPagedLookasideList(&RcaGlobal.SHPool.LookAsideList);
	
    RCADEBUGP(RCA_INFO, ("RCASHPoolFree(): exit\n"));
}