📄 write.cpp
字号:
/*++
Abstract:
This module contains the code that is very specific to write
operations in the serial driver
--*/
#include "precomp.h"
NTSTATUS KdSerialDevice::DispatchWrite(IN KdIrp &Irp)
/*++
Routine Description:
This is the dispatch routine for write. It validates the parameters
for the write request and if all is ok then it places the request
on the work queue.
Arguments:
Irp - Pointer to the IRP for the current request
Return Value:
If the io is zero length then it will return STATUS_SUCCESS,
otherwise this routine will return STATUS_PENDING.
--*/
{
DebugDump(DBG_DIAG6, ("Dispatch entry for: %x\n",Irp) );
if (CompleteIfError(Irp) != STATUS_SUCCESS)
return STATUS_CANCELLED;
Irp.Information() = 0L;
// Quick check for a zero length write. If it is zero length
// then we are already done!
if (Irp.WriteLength())
{
// Well it looks like we actually have to do some
// work. Put the write on the queue so that we can
// process it when our previous writes are done.
return StartOrQueue(
Irp,
&m_WriteQueue,
&m_CurrentWriteIrp,
StartWrite
);
}
else
{
Irp.Status() = STATUS_SUCCESS;
DebugDump(DBG_DIAG6, ("Complete Irp: %x\n",Irp) );
Irp.Complete();
return STATUS_SUCCESS;
}
}
NTSTATUS KdSerialDevice::StartWrite()
/*++
Routine Description:
This routine is used to start off any write. It initializes
the Iostatus fields of the irp. It will set up any timers
that are used to control the write.
Return Value:
This routine will return STATUS_PENDING for all writes
other than those that we find are cancelled.
--*/
{
KdIrp NewIrp;
KIRQL OldIrql;
LARGE_INTEGER TotalTime;
BOOLEAN UseATimer;
SERIAL_TIMEOUTS Timeouts;
BOOLEAN SetFirstStatus = FALSE;
NTSTATUS FirstStatus;
do {
// If there is an xoff counter then complete it.
IoAcquireCancelSpinLock(&OldIrql);
// We see if there is a actually an Xoff counter irp.
//
// If there is, we put the write irp back on the head
// of the write list. We then kill the xoff counter.
// The xoff counter killing code will actually make the
// xoff counter back into the current write irp, and
// in the course of completing the xoff (which is now
// the current write) we will restart this irp.
if (m_CurrentXoffIrp)
{
if (SERIAL_REFERENCE_COUNT(m_CurrentXoffIrp))
{
// The reference count is non-zero. This implies that
// the xoff irp has not made it through the completion
// path yet. We will increment the reference count
// and attempt to complete it ourselves.
SERIAL_SET_REFERENCE(
m_CurrentXoffIrp,
SERIAL_REF_XOFF_REF
);
// The following call will actually release the
// cancel spin lock.
TryToCompleteCurrent(
(KDIRQ_SYNC_CALLBACK) GrabXoffFromIsr,
OldIrql,
STATUS_SERIAL_MORE_WRITES,
&m_CurrentXoffIrp,
NULL,
NULL,
&m_KdTimer_XoffCountTimerDpc,
NULL,
NULL,
SERIAL_REF_XOFF_REF
);
}
else
{
// The irp is well on its way to being finished.
// We can let the regular completion code do the
// work. Just release the spin lock.
IoReleaseCancelSpinLock(OldIrql);
}
}
else
IoReleaseCancelSpinLock(OldIrql);
UseATimer = FALSE;
// Calculate the timeout value needed for the
// request. Note that the values stored in the
// timeout record are in milliseconds. Note that
// if the timeout values are zero then we won't start
// the timer.
m_ControlLock.Lock();
Timeouts = m_Timeouts;
m_ControlLock.Release();
if (Timeouts.WriteTotalTimeoutConstant ||
Timeouts.WriteTotalTimeoutMultiplier)
{
UseATimer = TRUE;
// We have some timer values to calculate.
//
// Take care, we might have an xoff counter masquerading
// as a write.
TotalTime.QuadPart =
(LONGLONG)((UInt32x32To64(
m_CurrentWriteIrp.MajorFunction() == IRP_MJ_WRITE ? m_CurrentWriteIrp.WriteLength() : 1 ,
Timeouts.WriteTotalTimeoutMultiplier
)
+ Timeouts.WriteTotalTimeoutConstant))
* -10000;
}
// The irp may be going to the isr shortly. Now
// is a good time to initialize its reference counts.
SERIAL_INIT_REFERENCE(m_CurrentWriteIrp);
// We need to see if this irp should be canceled.
IoAcquireCancelSpinLock(&OldIrql);
if (m_CurrentWriteIrp->Cancel)
{
IoReleaseCancelSpinLock(OldIrql);
m_CurrentWriteIrp.Status() = STATUS_CANCELLED;
if (!SetFirstStatus)
{
FirstStatus = STATUS_CANCELLED;
SetFirstStatus = TRUE;
}
}
else
{
if (!SetFirstStatus)
{
// If we haven't set our first status, then
// this is the only irp that could have possibly
// not been on the queue. (It could have been
// on the queue if this routine is being invoked
// from the completion routine.) Since this
// irp might never have been on the queue we
// should mark it as pending.
m_CurrentWriteIrp.MarkPending();
SetFirstStatus = TRUE;
FirstStatus = STATUS_PENDING;
}
// We give the irp to to the isr to write out.
// We set a cancel routine that knows how to
// grab the current write away from the isr.
//
// Since the cancel routine has an implicit reference
// to this irp up the reference count.
SET_IRP_CANCEL_ROUTINE(
m_CurrentWriteIrp,
CancelCurrentWrite
);
SERIAL_SET_REFERENCE(
m_CurrentWriteIrp,
SERIAL_REF_CANCEL
);
if (UseATimer)
{
m_KdTimer_WriteRequestTotalTimerDpc.Set(TotalTime);
// This timer now has a reference to the irp.
SERIAL_SET_REFERENCE(
m_CurrentWriteIrp,
SERIAL_REF_TOTAL_TIMER
);
}
m_KdInterrupt.SynchronizeExecution((KDIRQ_SYNC_CALLBACK)GiveWriteToIsr);
IoReleaseCancelSpinLock(OldIrql);
break;
}
// Well the write was canceled before we could start it up.
// Try to get another.
GetNextWrite(
&m_CurrentWriteIrp,
&m_WriteQueue,
&NewIrp,
TRUE
);
} while (NewIrp);
return FirstStatus;
}
VOID KdSerialDevice::GetNextWrite(
IN PKdIrp CurrentOpIrp,
IN PLIST_ENTRY QueueToProcess,
IN PKdIrp NewIrp,
IN BOOLEAN CompleteCurrent
)
/*++
Routine Description:
This routine completes the old write as well as getting
a pointer to the next write.
The reason that we have have pointers to the current write
queue as well as the current write irp is so that this
routine may be used in the common completion code for
read and write.
Arguments:
CurrentOpIrp - Pointer to the pointer that points to the
current write irp.
QueueToProcess - Pointer to the write queue.
NewIrp - A pointer to a pointer to the irp that will be the
current irp. Note that this could end up pointing
to a null pointer. This does NOT necessaryly mean
that there is no current write. What could occur
is that while the cancel lock is held the write
queue ended up being empty, but as soon as we release
the cancel spin lock a new irp came in from
SerialStartWrite.
CompleteCurrent - Flag indicates whether the CurrentOpIrp should
be completed.
--*/
{
PKdSerialDevice pDevice = CONTAINING_RECORD(
QueueToProcess,
KdSerialDevice,
m_WriteQueue
);
do {
// We could be completing a flush.
if (CurrentOpIrp->MajorFunction()==IRP_MJ_WRITE)
{
KIRQL OldIrql;
ASSERT(pDevice->m_TotalCharsQueued>=CurrentOpIrp->WriteLength());
IoAcquireCancelSpinLock(&OldIrql);
pDevice->m_TotalCharsQueued -= CurrentOpIrp->WriteLength();
IoReleaseCancelSpinLock(OldIrql);
} else if (CurrentOpIrp->MajorFunction()==IRP_MJ_DEVICE_CONTROL)
{
KIRQL OldIrql;
KdIrp Irp;
PSERIAL_XOFF_COUNTER Xc;
IoAcquireCancelSpinLock(&OldIrql);
Irp = *CurrentOpIrp;
Xc = (PSERIAL_XOFF_COUNTER)Irp.SystemBuffer();
// We should never have a xoff counter when we
// get to this point.
ASSERT(!pDevice->m_CurrentXoffIrp);
// We absolutely shouldn't have a cancel routine
// at this point.
ASSERT(!Irp->CancelRoutine);
// This could only be a xoff counter masquerading as
// a write irp.
pDevice->m_TotalCharsQueued--;
// Check to see of the xoff irp has been set with success.
// This means that the write completed normally. If that
// is the case, and it hasn't been set to cancel in the
// meanwhile, then go on and make it the CurrentXoffIrp.
if (Irp.Status() != STATUS_SUCCESS)
{
// Oh well, we can just finish it off.
NOTHING;
}
else if (Irp->Cancel)
Irp.Status() = STATUS_CANCELLED;
else
{
// Give it a new cancel routine, and increment the
// reference count because the cancel routine has
// a reference to it.
SET_IRP_CANCEL_ROUTINE(
Irp,
CancelCurrentXoff
);
SERIAL_SET_REFERENCE(
Irp,
SERIAL_REF_CANCEL
);
// We don't want to complete the current irp now. This
// will now get completed by the Xoff counter code.
CompleteCurrent = FALSE;
// Give the counter to the isr.
pDevice->m_CurrentXoffIrp = Irp;
pDevice->m_KdInterrupt.SynchronizeExecution((KDIRQ_SYNC_CALLBACK) pDevice->GiveXoffToIsr);
// Start the timer for the counter and increment
// the reference count since the timer has a
// reference to the irp.
if (Xc->Timeout)
{
LARGE_INTEGER delta;
delta.QuadPart = -((LONGLONG)UInt32x32To64(
1000,
Xc->Timeout
));
pDevice->m_KdTimer_XoffCountTimerDpc.Set(delta);
SERIAL_SET_REFERENCE(
Irp,
SERIAL_REF_TOTAL_TIMER
);
}
}
IoReleaseCancelSpinLock(OldIrql);
}
// Note that the following call will (probably) also cause
// the current irp to be completed.
GetNextIrp(
CurrentOpIrp,
QueueToProcess,
NewIrp,
CompleteCurrent
);
if (!*NewIrp)
{
KIRQL OldIrql;
IoAcquireCancelSpinLock(&OldIrql);
pDevice->m_KdInterrupt.SynchronizeExecution((KDIRQ_SYNC_CALLBACK) pDevice->ProcessEmptyTransmit);
IoReleaseCancelSpinLock(OldIrql);
break;
}
else if (NewIrp->MajorFunction()==IRP_MJ_FLUSH_BUFFERS)
{
// If we encounter a flush request we just want to get
// the next irp and complete the flush.
//
// Note that if NewIrp is non-null then it is also
// equal to CurrentWriteIrp.
ASSERT((*NewIrp) == (*CurrentOpIrp));
NewIrp->Status() = STATUS_SUCCESS;
}
else
break;
} while (TRUE);
}
VOID KdSerialDevice::CompleteWrite(
IN PKdDpc Dpc,
IN PVOID SystemContext1,
IN PVOID SystemContext2
)
/*++
Routine Description:
This routine is merely used to complete any write. It
assumes that the status and the information fields of
the irp are already correctly filled in.
Arguments:
Dpc - Not Used.
SystemContext1 - Not Used.
SystemContext2 - Not Used.
--*/
{
KIRQL OldIrql;
IoAcquireCancelSpinLock(&OldIrql);
TryToCompleteCurrent(
(KDIRQ_SYNC_CALLBACK) NULL,
OldIrql,
STATUS_SUCCESS,
&m_CurrentWriteIrp,
&m_WriteQueue,
NULL,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -