windows-nt/Source/XPSP1/NT/net/irda/comm/vuart/send.c
2020-09-26 16:20:57 +08:00

943 lines
20 KiB
C

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
send.c
Abstract:
This module contains the code that is very specific to initialization
and unload operations in the irenum driver
Author:
Brian Lieuallen, 7-13-2000
Environment:
Kernel mode
Revision History :
--*/
#include "internal.h"
VOID
RemoveReferenceForBuffers(
PSEND_TRACKER SendTracker
);
VOID
ProcessSend(
PTDI_CONNECTION Connection
);
VOID
ProcessSendAtPassive(
PTDI_CONNECTION Connection
);
VOID
SendBufferToTdi(
PFILE_OBJECT FileObject,
PIRCOMM_BUFFER Buffer
);
NTSTATUS
SendCompletion(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
VOID
TryToCompleteCurrentIrp(
PSEND_TRACKER SendTracker
);
VOID
RemoveReferenceOnTracker(
PSEND_TRACKER SendTracker
)
{
LONG Count;
Count=InterlockedDecrement(&SendTracker->ReferenceCount);
if (Count == 0) {
REMOVE_REFERENCE_TO_CONNECTION(SendTracker->Connection);
FREE_POOL(SendTracker);
}
return;
}
PIRP
GetCurrentIrpAndAddReference(
PSEND_TRACKER SendTracker
)
{
KIRQL OldIrql;
PIRP Irp;
KeAcquireSpinLock(
&SendTracker->Connection->Send.ControlLock,
&OldIrql
);
Irp=SendTracker->CurrentWriteIrp;
if (Irp != NULL) {
//
// irp is still around, add a ref coun to keep it around for a while.
//
SendTracker->IrpReferenceCount++;
}
KeReleaseSpinLock(
&SendTracker->Connection->Send.ControlLock,
OldIrql
);
return Irp;
}
VOID
ReleaseIrpReference(
PSEND_TRACKER SendTracker
)
{
KIRQL OldIrql;
PIRP Irp=NULL;
CONNECTION_CALLBACK Callback=NULL;
PVOID Context;
KeAcquireSpinLock(
&SendTracker->Connection->Send.ControlLock,
&OldIrql
);
SendTracker->IrpReferenceCount--;
if (SendTracker->IrpReferenceCount==0) {
//
// done, with irp complete it now with the current status
//
Irp=SendTracker->CurrentWriteIrp;
SendTracker->CurrentWriteIrp=NULL;
Callback=SendTracker->CompletionRoutine;
Context=SendTracker->CompletionContext;
#if DBG
SendTracker->CompletionRoutine=NULL;
#endif
SendTracker->Connection->Send.CurrentSendTracker=NULL;
}
KeReleaseSpinLock(
&SendTracker->Connection->Send.ControlLock,
OldIrql
);
if (Irp != NULL) {
//
// The ref count has gone to zero, complete the irp
//
(Callback)(
Context,
Irp
);
//
// release the reference for the irp
//
RemoveReferenceOnTracker(SendTracker);
}
return;
}
VOID
SetIrpAndRefcounts(
PSEND_TRACKER SendTracker,
PIRP Irp
)
{
//
// set the tracker refcount to 2, one for the irp, and one for the rountine that called this
//
SendTracker->ReferenceCount=2;
//
// Set the irp count to one for the rountine that called this, it will release when it done
// setting up the tracker block
//
SendTracker->IrpReferenceCount=1;
//
// save the irp
//
SendTracker->CurrentWriteIrp=Irp;
return;
}
VOID
SendTimerProc(
PKDPC Dpc,
PVOID Context,
PVOID SystemParam1,
PVOID SystemParam2
)
{
PSEND_TRACKER SendTracker=Context;
PIRP Irp;
KIRQL OldIrql;
D_ERROR(DbgPrint("IRCOMM: SendTimerProc\n");)
ASSERT(SendTracker->TimerSet);
#if DBG
SendTracker->TimerExpired=TRUE;
#endif
SendTracker->TimerSet=FALSE;
//
// try to get a hold of the irp so we can set the status
//
Irp=GetCurrentIrpAndAddReference(SendTracker);
Irp->IoStatus.Status=STATUS_TIMEOUT;
//
// release on reference for the one we just added
//
ReleaseIrpReference(SendTracker);
TryToCompleteCurrentIrp(
SendTracker
);
//
// release the second reference for the timer being set in the first place
//
ReleaseIrpReference(SendTracker);
return;
}
VOID
SendCancelRoutine(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
)
{
PSEND_TRACKER SendTracker=Irp->Tail.Overlay.DriverContext[0];
KIRQL OldIrql;
D_ERROR(DbgPrint("IRCOMM: SendCancelRoutine\n");)
#if DBG
SendTracker->IrpCanceled=TRUE;
#endif
IoReleaseCancelSpinLock(Irp->CancelIrql);
Irp->IoStatus.Status=STATUS_CANCELLED;
//
// clean up the timer
//
TryToCompleteCurrentIrp(SendTracker);
//
// release the reference held for the cancel routine
//
ReleaseIrpReference(SendTracker);
//
// send tracker maybe freed at this point
//
return;
}
VOID
TryToCompleteCurrentIrp(
PSEND_TRACKER SendTracker
)
{
KIRQL OldIrql;
PVOID OldCancelRoutine=NULL;
PIRP Irp;
BOOLEAN TimerCanceled=FALSE;
Irp=GetCurrentIrpAndAddReference(SendTracker);
KeAcquireSpinLock(
&SendTracker->Connection->Send.ControlLock,
&OldIrql
);
if (SendTracker->TimerSet) {
TimerCanceled=KeCancelTimer(
&SendTracker->Timer
);
if (TimerCanceled) {
//
// We ended up canceling the timer
//
SendTracker->TimerSet=FALSE;
} else {
//
// The timer is already running, we will just let it complete
// and do the clean up.
//
}
}
if (Irp != NULL) {
//
// the irp has not already been completed
//
OldCancelRoutine=IoSetCancelRoutine(
Irp,
NULL
);
}
KeReleaseSpinLock(
&SendTracker->Connection->Send.ControlLock,
OldIrql
);
if (TimerCanceled) {
//
// we canceled the timer before it could run, remove the reference for it
//
ReleaseIrpReference(SendTracker);
}
if (Irp != NULL) {
if (OldCancelRoutine != NULL) {
//
// since the cancel rountine had not run, release its reference to the irp
//
ReleaseIrpReference(SendTracker);
}
//
// if this routine got the irp, release the reference to it
//
ReleaseIrpReference(SendTracker);
}
return;
}
VOID
SendOnConnection(
IRDA_HANDLE Handle,
PIRP Irp,
CONNECTION_CALLBACK Callback,
PVOID Context,
ULONG Timeout
)
{
PTDI_CONNECTION Connection=Handle;
PIO_STACK_LOCATION IrpSp=IoGetCurrentIrpStackLocation(Irp);
PSEND_TRACKER SendTracker;
BOOLEAN AlreadyCanceled;
KIRQL OldIrql;
KIRQL OldCancelIrql;
if (Connection->Send.CurrentSendTracker != NULL) {
//
// called when we already have an irp
//
(Callback)(
Context,
Irp
);
return;
}
SendTracker=ALLOCATE_NONPAGED_POOL(sizeof(*SendTracker));
if (SendTracker == NULL) {
Irp->IoStatus.Status=STATUS_INSUFFICIENT_RESOURCES;
(Callback)(
Context,
Irp
);
return;
}
RtlZeroMemory(SendTracker,sizeof(*SendTracker));
KeInitializeTimer(
&SendTracker->Timer
);
KeInitializeDpc(
&SendTracker->TimerDpc,
SendTimerProc,
SendTracker
);
//
// set the irp and initialize the refcounts
//
SetIrpAndRefcounts(SendTracker,Irp);
ADD_REFERENCE_TO_CONNECTION(Connection);
//
// initialize these values
//
SendTracker->Connection=Connection;
SendTracker->BuffersOutstanding=0;
SendTracker->CompletionContext = Context;
SendTracker->CompletionRoutine = Callback;
SendTracker->BytesRemainingInIrp = IrpSp->Parameters.Write.Length;
if (Timeout > 0) {
//
// add a reference for the timer
//
GetCurrentIrpAndAddReference(SendTracker);
}
//
// add a reference to the irp for the cancel rountine
//
GetCurrentIrpAndAddReference(SendTracker);
KeAcquireSpinLock(
&Connection->Send.ControlLock,
&OldIrql
);
Connection->Send.CurrentSendTracker=SendTracker;
if (Timeout > 0) {
//
// need to set a timer
//
LARGE_INTEGER DueTime;
DueTime.QuadPart= (LONGLONG)(Timeout+100) * -10000;
SendTracker->TimerSet=TRUE;
KeSetTimer(
&SendTracker->Timer,
DueTime,
&SendTracker->TimerDpc
);
}
Irp->Tail.Overlay.DriverContext[0]=SendTracker;
IoAcquireCancelSpinLock(&OldCancelIrql);
AlreadyCanceled=Irp->Cancel;
if (!AlreadyCanceled) {
PIRCOMM_BUFFER Buffer;
//
// the irp has not been canceled already, set the cancel routine
//
IoSetCancelRoutine(
Irp,
SendCancelRoutine
);
} else {
//
// it was canceled when we got it
//
Irp->IoStatus.Status=STATUS_CANCELLED;
}
IoReleaseCancelSpinLock(OldCancelIrql);
KeReleaseSpinLock(
&Connection->Send.ControlLock,
OldIrql
);
if (AlreadyCanceled) {
//
// The irp has already been canceled, just call the cancel rountine so the normal code runs
//
D_ERROR(DbgPrint("IRCOMM: SendOnConnection: irp already canceled\n");)
IoAcquireCancelSpinLock(&Irp->CancelIrql);
SendCancelRoutine(
NULL,
Irp
);
//
// the cancel rountine will release the cancel spinlock
//
}
//
// release the reference for this routine
//
ReleaseIrpReference(SendTracker);
ProcessSendAtPassive(Connection);
RemoveReferenceOnTracker(SendTracker);
return;
}
VOID
ProcessSendAtPassive(
PTDI_CONNECTION Connection
)
{
if (KeGetCurrentIrql() < DISPATCH_LEVEL) {
//
// less than dispatch, just call directly
//
ProcessSend(Connection);
} else {
//
// Called at dispatch, queue the work item
//
LONG Count=InterlockedIncrement(&Connection->Send.WorkItemCount);
if (Count == 1) {
ExQueueWorkItem(&Connection->Send.WorkItem,CriticalWorkQueue);
}
}
return;
}
VOID
SendWorkItemRountine(
PVOID Context
)
{
PTDI_CONNECTION Connection=Context;
//
// the work item has run set the count to zero
//
InterlockedExchange(&Connection->Send.WorkItemCount,0);
ProcessSend(Connection);
}
VOID
ProcessSend(
PTDI_CONNECTION Connection
)
{
PSEND_TRACKER SendTracker;
PIRP Irp;
PIO_STACK_LOCATION IrpSp;
PLIST_ENTRY ListEntry;
ULONG BytesUsedInBuffer;
PIRCOMM_BUFFER Buffer;
BOOLEAN ExitLoop;
PFILE_OBJECT FileObject;
CONNECTION_HANDLE ConnectionHandle;
KIRQL OldIrql;
KeAcquireSpinLock(
&Connection->Send.ControlLock,
&OldIrql
);
if (Connection->Send.ProcessSendEntryCount == 0) {
Connection->Send.ProcessSendEntryCount++;
while ((Connection->Send.CurrentSendTracker != NULL)
&&
(!Connection->Send.OutOfBuffers)
&&
(Connection->LinkUp)
&&
(Connection->Send.CurrentSendTracker->BytesRemainingInIrp > 0)) {
SendTracker=Connection->Send.CurrentSendTracker;
InterlockedIncrement(&SendTracker->ReferenceCount);
KeReleaseSpinLock(
&Connection->Send.ControlLock,
OldIrql
);
Irp=GetCurrentIrpAndAddReference(SendTracker);
if (Irp != NULL) {
//
// got the current irp
//
IrpSp=IoGetCurrentIrpStackLocation(Irp);
ConnectionHandle=GetCurrentConnection(Connection->LinkHandle);
if (ConnectionHandle != NULL) {
//
// we have a good connection
//
FileObject=ConnectionGetFileObject(ConnectionHandle);
Buffer=ConnectionGetBuffer(ConnectionHandle,BUFFER_TYPE_SEND);
if (Buffer != NULL) {
LONG BytesToCopy=min(SendTracker->BytesRemainingInIrp, (LONG)Buffer->BufferLength - 1);
//
// this buffer is going to be outstanding, set this before the bytes
// remaining count goes to zero
//
InterlockedIncrement(&SendTracker->BuffersOutstanding);
//
// start with a zero length of control bytes
//
Buffer->Data[0]=0;
//
// actual data starts one byte in, after the length byte
//
// move the data
//
RtlCopyMemory(
&Buffer->Data[1],
(PUCHAR)Irp->AssociatedIrp.SystemBuffer+(IrpSp->Parameters.Write.Length - SendTracker->BytesRemainingInIrp),
BytesToCopy
);
//
// the count has to include the control byte
//
Buffer->Mdl->ByteCount= 1 + BytesToCopy;
#if DBG
RtlFillMemory(
&Buffer->Data[Buffer->Mdl->ByteCount],
Buffer->BufferLength-Buffer->Mdl->ByteCount,
0xfb
);
#endif
InterlockedExchangeAdd(&SendTracker->BytesRemainingInIrp, -BytesToCopy);
Buffer->Mdl->Next=NULL;
Buffer->Context=SendTracker;
InterlockedIncrement(&SendTracker->ReferenceCount);
ASSERT(SendTracker->CurrentWriteIrp != NULL);
SendBufferToTdi(
FileObject,
Buffer
);
#if DBG
Buffer=NULL;
#endif
} else {
//
// No more buffers availible
//
Connection->Send.OutOfBuffers=TRUE;
}
ConnectionReleaseFileObject(ConnectionHandle,FileObject);
ReleaseConnection(ConnectionHandle);
} else {
//
// The connection, must be down
//
D_ERROR(DbgPrint("IRCOMM: ProcessSend: Link down\n");)
Connection->LinkUp=FALSE;
}
ReleaseIrpReference(SendTracker);
} else {
//
// the irp has already been completed from this tracking block
//
D_ERROR(DbgPrint("IRCOMM: ProcessSend: no irp\n");)
ASSERT(SendTracker->TimerExpired || SendTracker->IrpCanceled || SendTracker->SendAborted);
}
RemoveReferenceOnTracker(SendTracker);
KeAcquireSpinLock(
&Connection->Send.ControlLock,
&OldIrql
);
} // while
Connection->Send.ProcessSendEntryCount--;
}
KeReleaseSpinLock(
&Connection->Send.ControlLock,
OldIrql
);
return;
}
VOID
SendBufferToTdi(
PFILE_OBJECT FileObject,
PIRCOMM_BUFFER Buffer
)
{
PDEVICE_OBJECT IrdaDeviceObject=IoGetRelatedDeviceObject(FileObject);
ULONG SendLength;
IoReuseIrp(Buffer->Irp,STATUS_SUCCESS);
Buffer->Irp->Tail.Overlay.OriginalFileObject = FileObject;
SendLength = MmGetMdlByteCount(Buffer->Mdl);
TdiBuildSend(
Buffer->Irp,
IrdaDeviceObject,
FileObject,
SendCompletion,
Buffer,
Buffer->Mdl,
0, // send flags
SendLength
);
IoCallDriver(IrdaDeviceObject, Buffer->Irp);
return;
}
NTSTATUS
SendCompletion(
PDEVICE_OBJECT DeviceObject,
PIRP BufferIrp,
PVOID Context
)
{
PIRCOMM_BUFFER Buffer=Context;
PSEND_TRACKER SendTracker=Buffer->Context;
PTDI_CONNECTION Connection=SendTracker->Connection;
LONG BuffersOutstanding;
//
// save off the status for the sub transerfer
//
SendTracker->LastStatus=BufferIrp->IoStatus.Status;
D_TRACE(DbgPrint("IRCOMM: SendComplete: Status=%08lx, len=%d\n",BufferIrp->IoStatus.Status,BufferIrp->IoStatus.Information);)
#if DBG
RtlFillMemory(
&Buffer->Data[0],
Buffer->BufferLength,
0xfe
);
#endif
//
// return the buffer
//
Buffer->FreeBuffer(Buffer);
Connection->Send.OutOfBuffers=FALSE;
#if DBG
Buffer=NULL;
BufferIrp=NULL;
#endif
BuffersOutstanding=InterlockedDecrement(&SendTracker->BuffersOutstanding);
if ((BuffersOutstanding == 0) && (SendTracker->BytesRemainingInIrp == 0)) {
//
// All of the data in the irp has been sent and all of the irps send to
// the irda stack have completed
//
// Done with the irp in this tracker
//
PIRP Irp;
PIO_STACK_LOCATION IrpSp;
Irp=GetCurrentIrpAndAddReference(SendTracker);
if (Irp != NULL) {
IrpSp=IoGetCurrentIrpStackLocation(Irp);
Irp->IoStatus.Information=IrpSp->Parameters.Write.Length;
Irp->IoStatus.Status=SendTracker->LastStatus;
ReleaseIrpReference(SendTracker);
TryToCompleteCurrentIrp(SendTracker);
} else {
//
// the irp has already been completed from this tracking block
//
D_ERROR(DbgPrint("IRCOMM: SendCompletion: no irp\n");)
//
// this should only happen if the timer expired or the irp was canceled
//
ASSERT(SendTracker->TimerExpired || SendTracker->IrpCanceled || SendTracker->SendAborted);
}
}
RemoveReferenceOnTracker(SendTracker);
ProcessSendAtPassive(Connection);
return STATUS_MORE_PROCESSING_REQUIRED;
}
VOID
AbortSend(
IRDA_HANDLE Handle
)
{
PTDI_CONNECTION Connection=Handle;
PSEND_TRACKER SendTracker=NULL;
KIRQL OldIrql;
KeAcquireSpinLock(
&Connection->Send.ControlLock,
&OldIrql
);
SendTracker=Connection->Send.CurrentSendTracker;
if (SendTracker != NULL) {
InterlockedIncrement(&SendTracker->ReferenceCount);
}
KeReleaseSpinLock(
&Connection->Send.ControlLock,
OldIrql
);
if (SendTracker != NULL) {
#if DBG
SendTracker->SendAborted=TRUE;
#endif
TryToCompleteCurrentIrp(SendTracker);
RemoveReferenceOnTracker(SendTracker);
}
return;
}