/*-------------------------------------------------------------------------- * * Copyright (C) Cyclades Corporation, 1996-1999. * All rights reserved. * * Cyclom-Y Port Driver * * This file: cyywrite.c * * Description: This module contains the code related to write * operations in the Cyclom-Y Port driver. * * Notes: This code supports Windows 2000 and Windows XP, * x86 and IA64 processors. * * Complies with Cyclades SW Coding Standard rev 1.3. * *-------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * * Change History * *-------------------------------------------------------------------------- * * *-------------------------------------------------------------------------- */ #include "precomp.h" BOOLEAN CyyGiveWriteToIsr( IN PVOID Context ); VOID CyyCancelCurrentWrite( PDEVICE_OBJECT DeviceObject, PIRP Irp ); BOOLEAN CyyGrabWriteFromIsr( IN PVOID Context ); BOOLEAN CyyGrabXoffFromIsr( IN PVOID Context ); VOID CyyCancelCurrentXoff( PDEVICE_OBJECT DeviceObject, PIRP Irp ); BOOLEAN CyyGiveXoffToIsr( IN PVOID Context ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGESER,CyyProcessEmptyTransmit) #pragma alloc_text(PAGESER,CyyWrite) #pragma alloc_text(PAGESER,CyyStartWrite) #pragma alloc_text(PAGESER,CyyGetNextWrite) #pragma alloc_text(PAGESER,CyyGiveWriteToIsr) #pragma alloc_text(PAGESER,CyyCancelCurrentWrite) #pragma alloc_text(PAGESER,CyyGrabWriteFromIsr) #pragma alloc_text(PAGESER,CyyGrabXoffFromIsr) #pragma alloc_text(PAGESER,CyyCancelCurrentXoff) #pragma alloc_text(PAGESER,CyyGiveXoffToIsr) #endif NTSTATUS CyyWrite( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*-------------------------------------------------------------------------- CyyWrite() 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: DeviceObject - Pointer to the device object for this device 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. --------------------------------------------------------------------------*/ { PCYY_DEVICE_EXTENSION Extension = DeviceObject->DeviceExtension; NTSTATUS status; CYY_LOCKED_PAGED_CODE(); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, ">CyyWrite(%X, %X)\n", DeviceObject, Irp); if ((status = CyyIRPPrologue(Irp, Extension)) != STATUS_SUCCESS) { if (status != STATUS_PENDING) { CyyCompleteRequest(Extension, Irp, IO_NO_INCREMENT); } CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "IoStatus.Information = 0L; // // Quick check for a zero length write. If it is zero length // then we are already done! // if (IoGetCurrentIrpStackLocation(Irp)->Parameters.Write.Length) { // // 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. // status = CyyStartOrQueue(Extension, Irp, &Extension->WriteQueue, &Extension->CurrentWriteIrp, CyyStartWrite); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "IoStatus.Status = STATUS_SUCCESS; CyyCompleteRequest(Extension, Irp, 0); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "CyyStartWrite(%X)\n", Extension); 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 (Extension->CurrentXoffIrp) { if (SERIAL_REFERENCE_COUNT(Extension->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 ourseleves. SERIAL_SET_REFERENCE(Extension->CurrentXoffIrp, SERIAL_REF_XOFF_REF); Extension->CurrentXoffIrp->IoStatus.Information = 0; // Added in build 2128 // The following call will actually release the // cancel spin lock. CyyTryToCompleteCurrent( Extension, CyyGrabXoffFromIsr, OldIrql, STATUS_SERIAL_MORE_WRITES, &Extension->CurrentXoffIrp, NULL, NULL, &Extension->XoffCountTimer, 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. KeAcquireSpinLock(&Extension->ControlLock,&OldIrql); Timeouts = Extension->Timeouts; KeReleaseSpinLock(&Extension->ControlLock,OldIrql); if (Timeouts.WriteTotalTimeoutConstant || Timeouts.WriteTotalTimeoutMultiplier) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Extension->CurrentWriteIrp ); 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( (IrpSp->MajorFunction == IRP_MJ_WRITE)? (IrpSp->Parameters.Write.Length): (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(Extension->CurrentWriteIrp); // We need to see if this irp should be canceled. IoAcquireCancelSpinLock(&OldIrql); if (Extension->CurrentWriteIrp->Cancel) { IoReleaseCancelSpinLock(OldIrql); Extension->CurrentWriteIrp->IoStatus.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. IoMarkIrpPending(Extension->CurrentWriteIrp); 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. IoSetCancelRoutine( Extension->CurrentWriteIrp, CyyCancelCurrentWrite ); SERIAL_SET_REFERENCE(Extension->CurrentWriteIrp,SERIAL_REF_CANCEL); if (UseATimer) { CyySetTimer( &Extension->WriteRequestTotalTimer, TotalTime, &Extension->TotalWriteTimeoutDpc, Extension ); // This timer now has a reference to the irp. SERIAL_SET_REFERENCE( Extension->CurrentWriteIrp, SERIAL_REF_TOTAL_TIMER ); } KeSynchronizeExecution( Extension->Interrupt, CyyGiveWriteToIsr, Extension ); IoReleaseCancelSpinLock(OldIrql); break; } // Well the write was canceled before we could start it up. // Try to get another. CyyGetNextWrite(&Extension->CurrentWriteIrp, &Extension->WriteQueue, &NewIrp, TRUE, Extension); } while (NewIrp); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "CyyGetNextWrite(XXX)\n"); do { // // We could be completing a flush. // if (IoGetCurrentIrpStackLocation(*CurrentOpIrp)->MajorFunction == IRP_MJ_WRITE) { KIRQL OldIrql; ASSERT(Extension->TotalCharsQueued >= (IoGetCurrentIrpStackLocation(*CurrentOpIrp) ->Parameters.Write.Length)); IoAcquireCancelSpinLock(&OldIrql); Extension->TotalCharsQueued -= IoGetCurrentIrpStackLocation(*CurrentOpIrp) ->Parameters.Write.Length; IoReleaseCancelSpinLock(OldIrql); } else if (IoGetCurrentIrpStackLocation(*CurrentOpIrp)->MajorFunction == IRP_MJ_DEVICE_CONTROL) { KIRQL OldIrql; PIRP Irp; PSERIAL_XOFF_COUNTER Xc; IoAcquireCancelSpinLock(&OldIrql); Irp = *CurrentOpIrp; Xc = Irp->AssociatedIrp.SystemBuffer; // // We should never have a xoff counter when we // get to this point. // ASSERT(!Extension->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. // Extension->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->IoStatus.Status != STATUS_SUCCESS) { // // Oh well, we can just finish it off. // NOTHING; } else if (Irp->Cancel) { Irp->IoStatus.Status = STATUS_CANCELLED; } else { // // Give it a new cancel routine, and increment the // reference count because the cancel routine has // a reference to it. // IoSetCancelRoutine( Irp, CyyCancelCurrentXoff ); 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. // Extension->CurrentXoffIrp = Irp; KeSynchronizeExecution( Extension->Interrupt, CyyGiveXoffToIsr, Extension ); // // 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 )); CyySetTimer( &Extension->XoffCountTimer, delta, &Extension->XoffCountTimeoutDpc, Extension ); 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. // CyyGetNextIrp( CurrentOpIrp, QueueToProcess, NewIrp, CompleteCurrent, Extension ); if (!*NewIrp) { KIRQL OldIrql; IoAcquireCancelSpinLock(&OldIrql); KeSynchronizeExecution( Extension->Interrupt, CyyProcessEmptyTransmit, Extension ); IoReleaseCancelSpinLock(OldIrql); break; } else if (IoGetCurrentIrpStackLocation(*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)->IoStatus.Status = STATUS_SUCCESS; } else { break; } } while (TRUE); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "CyyCompleteWrite(%X)\n", Extension); IoAcquireCancelSpinLock(&OldIrql); CyyTryToCompleteCurrent(Extension, NULL, OldIrql, STATUS_SUCCESS, &Extension->CurrentWriteIrp, &Extension->WriteQueue, NULL, &Extension->WriteRequestTotalTimer, CyyStartWrite, CyyGetNextWrite, SERIAL_REF_ISR); CyyDpcEpilogue(Extension, Dpc); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "IsrWaitMask && (Extension->IsrWaitMask & SERIAL_EV_TXEMPTY) && Extension->EmptiedTransmit && (!Extension->TransmitImmediate) && (!Extension->CurrentWriteIrp) && IsListEmpty(&Extension->WriteQueue)) { Extension->HistoryMask |= SERIAL_EV_TXEMPTY; if (Extension->IrpMaskLocation) { *Extension->IrpMaskLocation = Extension->HistoryMask; Extension->IrpMaskLocation = NULL; Extension->HistoryMask = 0; Extension->CurrentWaitIrp->IoStatus.Information = sizeof(ULONG); CyyInsertQueueDpc( &Extension->CommWaitDpc, NULL, NULL, Extension ); } Extension->CountOfTryingToLowerRTS++; CyyPerhapsLowerRTS(Extension); } return FALSE; } BOOLEAN CyyGiveWriteToIsr( IN PVOID Context ) /*++ Routine Description: Try to start off the write by slipping it in behind a transmit immediate char, or if that isn't available and the transmit holding register is empty, "tickle" the UART into interrupting with a transmit buffer empty. NOTE: This routine is called by KeSynchronizeExecution. NOTE: This routine assumes that it is called with the cancel spin lock held. Arguments: Context - Really a pointer to the device extension. Return Value: This routine always returns FALSE. --*/ { PCYY_DEVICE_EXTENSION Extension = Context; // // The current stack location. This contains all of the // information we need to process this particular request. // PIO_STACK_LOCATION IrpSp; CYY_LOCKED_PAGED_CODE(); IrpSp = IoGetCurrentIrpStackLocation(Extension->CurrentWriteIrp); // // We might have a xoff counter request masquerading as a // write. The length of these requests will always be one // and we can get a pointer to the actual character from // the data supplied by the user. // if (IrpSp->MajorFunction == IRP_MJ_WRITE) { Extension->WriteLength = IrpSp->Parameters.Write.Length; Extension->WriteCurrentChar = Extension->CurrentWriteIrp->AssociatedIrp.SystemBuffer; } else { Extension->WriteLength = 1; Extension->WriteCurrentChar = ((PUCHAR)Extension->CurrentWriteIrp->AssociatedIrp.SystemBuffer) + FIELD_OFFSET( SERIAL_XOFF_COUNTER, XoffChar ); } // // The isr now has a reference to the irp. // SERIAL_SET_REFERENCE( Extension->CurrentWriteIrp, SERIAL_REF_ISR ); // // Check first to see if an immediate char is transmitting. // If it is then we'll just slip in behind it when its // done. // if (!Extension->TransmitImmediate) { // // If there is no immediate char transmitting then we // will "re-enable" the transmit holding register empty // interrupt. The 8250 family of devices will always // signal a transmit holding register empty interrupt // *ANY* time this bit is set to one. By doing things // this way we can simply use the normal interrupt code // to start off this write. // // We've been keeping track of whether the transmit holding // register is empty so it we only need to do this // if the register is empty. // if (Extension->HoldingEmpty) { // enable transmit intr CyyTxStart(Extension); } } // // The rts line may already be up from previous writes, // however, it won't take much additional time to turn // on the RTS line if we are doing transmit toggling. // if ((Extension->HandFlow.FlowReplace & SERIAL_RTS_MASK) == SERIAL_TRANSMIT_TOGGLE) { CyySetRTS(Extension); } return FALSE; } VOID CyyCancelCurrentWrite( PDEVICE_OBJECT DeviceObject, PIRP Irp ) /*++ Routine Description: This routine is used to cancel the current write. Arguments: DeviceObject - Pointer to the device object for this device Irp - Pointer to the IRP to be canceled. Return Value: None. --*/ { PCYY_DEVICE_EXTENSION Extension = DeviceObject->DeviceExtension; CYY_LOCKED_PAGED_CODE(); CyyTryToCompleteCurrent( Extension, CyyGrabWriteFromIsr, Irp->CancelIrql, STATUS_CANCELLED, &Extension->CurrentWriteIrp, &Extension->WriteQueue, NULL, &Extension->WriteRequestTotalTimer, CyyStartWrite, CyyGetNextWrite, SERIAL_REF_CANCEL ); } VOID CyyWriteTimeout( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemContext1, IN PVOID SystemContext2 ) /*++ Routine Description: This routine will try to timeout the current write. Arguments: Dpc - Not Used. DeferredContext - Really points to the device extension. SystemContext1 - Not Used. SystemContext2 - Not Used. Return Value: None. --*/ { PCYY_DEVICE_EXTENSION Extension = DeferredContext; KIRQL OldIrql; UNREFERENCED_PARAMETER(SystemContext1); UNREFERENCED_PARAMETER(SystemContext2); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, ">CyyWriteTimeout(%X)\n", Extension); IoAcquireCancelSpinLock(&OldIrql); CyyTryToCompleteCurrent(Extension, CyyGrabWriteFromIsr, OldIrql, STATUS_TIMEOUT, &Extension->CurrentWriteIrp, &Extension->WriteQueue, NULL, &Extension->WriteRequestTotalTimer, CyyStartWrite, CyyGetNextWrite, SERIAL_REF_TOTAL_TIMER); CyyDpcEpilogue(Extension, Dpc); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "WriteLength) { // // We could have an xoff counter masquerading as a // write irp. If so, don't update the write length. // if (IoGetCurrentIrpStackLocation(Extension->CurrentWriteIrp) ->MajorFunction == IRP_MJ_WRITE) { Extension->CurrentWriteIrp->IoStatus.Information = IoGetCurrentIrpStackLocation( Extension->CurrentWriteIrp )->Parameters.Write.Length - Extension->WriteLength; } else { Extension->CurrentWriteIrp->IoStatus.Information = 0; } // // Since the isr no longer references this irp, we can // decrement it's reference count. // SERIAL_CLEAR_REFERENCE( Extension->CurrentWriteIrp, SERIAL_REF_ISR ); Extension->WriteLength = 0; } return FALSE; } BOOLEAN CyyGrabXoffFromIsr( IN PVOID Context ) /*++ Routine Description: This routine is used to grab an xoff counter irp from the isr when it is no longer masquerading as a write irp. This routine is called by the cancel and timeout code for the xoff counter ioctl. NOTE: This routine is being called from KeSynchronizeExecution. NOTE: This routine assumes that the cancel spin lock is held when this routine is called. Arguments: Context - Really a pointer to the device extension. Return Value: Always false. --*/ { PCYY_DEVICE_EXTENSION Extension = Context; CYY_LOCKED_PAGED_CODE(); if (Extension->CountSinceXoff) { // // This is only non-zero when there actually is a Xoff ioctl // counting down. // Extension->CountSinceXoff = 0; // // We decrement the count since the isr no longer owns // the irp. // SERIAL_CLEAR_REFERENCE( Extension->CurrentXoffIrp, SERIAL_REF_ISR ); } return FALSE; } VOID CyyCompleteXoff( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemContext1, IN PVOID SystemContext2 ) /*++ Routine Description: This routine is merely used to truely complete an xoff counter irp. It assumes that the status and the information fields of the irp are already correctly filled in. Arguments: Dpc - Not Used. DeferredContext - Really points to the device extension. SystemContext1 - Not Used. SystemContext2 - Not Used. Return Value: None. --*/ { PCYY_DEVICE_EXTENSION Extension = DeferredContext; KIRQL OldIrql; UNREFERENCED_PARAMETER(SystemContext1); UNREFERENCED_PARAMETER(SystemContext2); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, ">CyyCompleteXoff(%X)\n", Extension); IoAcquireCancelSpinLock(&OldIrql); CyyTryToCompleteCurrent(Extension, NULL, OldIrql, STATUS_SUCCESS, &Extension->CurrentXoffIrp, NULL, NULL, &Extension->XoffCountTimer, NULL, NULL, SERIAL_REF_ISR); CyyDpcEpilogue(Extension, Dpc); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "CyyTimeoutXoff(%X)\n", Extension); IoAcquireCancelSpinLock(&OldIrql); CyyTryToCompleteCurrent(Extension, CyyGrabXoffFromIsr, OldIrql, STATUS_SERIAL_COUNTER_TIMEOUT, &Extension->CurrentXoffIrp, NULL, NULL, NULL, NULL, NULL, SERIAL_REF_TOTAL_TIMER); CyyDpcEpilogue(Extension, Dpc); CyyDbgPrintEx(DPFLTR_TRACE_LEVEL, "DeviceExtension; CYY_LOCKED_PAGED_CODE(); CyyTryToCompleteCurrent( Extension, CyyGrabXoffFromIsr, Irp->CancelIrql, STATUS_CANCELLED, &Extension->CurrentXoffIrp, NULL, NULL, &Extension->XoffCountTimer, NULL, NULL, SERIAL_REF_CANCEL ); } BOOLEAN CyyGiveXoffToIsr( IN PVOID Context ) /*++ Routine Description: This routine starts off the xoff counter. It merely has to set the xoff count and increment the reference count to denote that the isr has a reference to the irp. NOTE: This routine is called by KeSynchronizeExecution. NOTE: This routine assumes that it is called with the cancel spin lock held. Arguments: Context - Really a pointer to the device extension. Return Value: This routine always returns FALSE. --*/ { PCYY_DEVICE_EXTENSION Extension = Context; // // The current stack location. This contains all of the // information we need to process this particular request. // PSERIAL_XOFF_COUNTER Xc = Extension->CurrentXoffIrp->AssociatedIrp.SystemBuffer; CYY_LOCKED_PAGED_CODE(); ASSERT(Extension->CurrentXoffIrp); Extension->CountSinceXoff = Xc->Counter; // // The isr now has a reference to the irp. // SERIAL_SET_REFERENCE( Extension->CurrentXoffIrp, SERIAL_REF_ISR ); return FALSE; }