|
|
/*++
Copyright (c) 1999 Microsoft Corporation
Module Name:
read.c
Abstract: ESC/POS (serial) interface for USB Point-of-Sale devices
Author:
ervinp
Environment:
Kernel mode
Revision History:
--*/
#include <WDM.H>
#include <usbdi.h>
#include <usbdlib.h>
#include <usbioctl.h>
#include "escpos.h"
#include "debug.h"
NTSTATUS ReadComPort(POSPDOEXT *pdoExt, PIRP irp) { NTSTATUS status; PIO_STACK_LOCATION currentIrpSp;
/*
* In order to support ODD ENDPOINTs, we check * whether this COM port has a read endpoint or not. */ if(!pdoExt->inputEndpointInfo.pipeHandle) { DBGVERBOSE(("This PORT does not have an IN endpoint - Read request Rejected.")); return STATUS_NOT_SUPPORTED; }
DBGVERBOSE(("ReadComPort"));
currentIrpSp = IoGetCurrentIrpStackLocation(irp);
ASSERT(currentIrpSp->Parameters.Read.Length); ASSERT(!irp->MdlAddress);
/*
* Because this device object uses buffering method METHOD_NEITHER, * the read buffer is irp->UserBuffer, which is potentially an application * read buffer. If the read completes on a different thread than this calling * thread, then the completion routine will not have the read buffer addressed * correctly. * Therefore, we have to map the UserBuffer using an MDL. */ irp->MdlAddress = MmCreateMdl(NULL, irp->UserBuffer, currentIrpSp->Parameters.Read.Length);
if (irp->MdlAddress){ status = STATUS_SUCCESS; __try { /*
* We're writing the read data to the buffer, so probe for WriteAccess. */ MmProbeAndLockPages(irp->MdlAddress, UserMode, IoWriteAccess); } __except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); DBGERR(("MmProbeAndLockPages triggered exception status %xh.", status)); }
if (NT_SUCCESS(status)){
status = EnqueueReadIrp(pdoExt, irp, FALSE, FALSE);
if (status == STATUS_PENDING){ BOOLEAN doReadNow; KIRQL oldIrql;
/*
* Atomically test-and-set the endpointIsBusy flag. * If the endpoint was not busy, issue a read after dropping the lock. */ KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql); if (pdoExt->inputEndpointInfo.endpointIsBusy){ doReadNow = FALSE; } else { pdoExt->inputEndpointInfo.endpointIsBusy = TRUE; doReadNow = TRUE; } KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
if (doReadNow){ IssueReadForClient(pdoExt); } }
} } else { DBGERR(("MmCreateMdl failed")); status = STATUS_DATA_ERROR; }
return status; }
VOID SatisfyPendingReads(POSPDOEXT *pdoExt) { LIST_ENTRY irpsToCompleteList, readPacketsToFree; PLIST_ENTRY listEntry; PIRP irp; READPACKET *readPacket; KIRQL oldIrql;
DBGVERBOSE(("SatisfyPendingReads"));
/*
* Accumulate the complete-ready IRPs on a private queue before completing. * This is so we don't loop forever if they get re-queued on the same thread. */ InitializeListHead(&irpsToCompleteList); InitializeListHead(&readPacketsToFree);
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
while (irp = DequeueReadIrp(pdoExt, TRUE)){
PIO_STACK_LOCATION currentIrpSp = IoGetCurrentIrpStackLocation(irp); BOOLEAN canSatisfyOneIrp;
/*
* Do we have enough bytes to satisfy this IRP ? */ #if PARTIAL_READ_BUFFERS_OK
canSatisfyOneIrp = (pdoExt->totalQueuedReadDataLength > 0); #else
canSatisfyOneIrp = (pdoExt->totalQueuedReadDataLength >= currentIrpSp->Parameters.Read.Length); #endif
if (canSatisfyOneIrp){
ULONG userBufferOffset = 0; BOOLEAN satisfiedThisIrp = FALSE; PUCHAR mappedUserBuffer;
ASSERT(irp->MdlAddress); ASSERT(!IsListEmpty(&pdoExt->completedReadPacketsList));
/*
* We may be completing this IRP on a different thread than the calling thread. * So we cannot dereference UserBuffer directly. * Use the MDL we created at call time instead. */ mappedUserBuffer = PosMmGetSystemAddressForMdlSafe(irp->MdlAddress);
if (mappedUserBuffer){
while (!IsListEmpty(&pdoExt->completedReadPacketsList) && (userBufferOffset < currentIrpSp->Parameters.Read.Length)){
ULONG bytesToCopy; BOOLEAN thisIrpFull;
listEntry = RemoveHeadList(&pdoExt->completedReadPacketsList); ASSERT(listEntry); readPacket = CONTAINING_RECORD(listEntry, READPACKET, listEntry); ASSERT(readPacket->signature == READPACKET_SIG);
bytesToCopy = MIN(currentIrpSp->Parameters.Read.Length-userBufferOffset, readPacket->length-readPacket->offset); ASSERT(bytesToCopy <= pdoExt->totalQueuedReadDataLength);
DBGVERBOSE(("SatisfyPendingReads: transferring %xh bytes to read irp", bytesToCopy));
/*
* Since we may be completing this IRP on a different thread than * the one we got it on, we cannot write into the UserBuffer. * We have to write into the MDL we allocated when we queued this IRP. */ RtlCopyMemory(mappedUserBuffer+userBufferOffset, readPacket->data+readPacket->offset, bytesToCopy);
userBufferOffset += bytesToCopy; readPacket->offset += bytesToCopy; pdoExt->totalQueuedReadDataLength -= bytesToCopy;
ASSERT(userBufferOffset <= currentIrpSp->Parameters.Read.Length); ASSERT(readPacket->offset <= readPacket->length);
#if PARTIAL_READ_BUFFERS_OK
thisIrpFull = (userBufferOffset > 0); #else
thisIrpFull = (userBufferOffset >= currentIrpSp->Parameters.Read.Length); #endif
if (thisIrpFull){ /*
* We've satisfied this IRP. * Break out of the inner loop so we get a new IRP. * Put the readPacket back in its queue in case there * are more bytes left in it. */ irp->IoStatus.Information = userBufferOffset; irp->IoStatus.Status = STATUS_SUCCESS; InsertTailList(&irpsToCompleteList, &irp->Tail.Overlay.ListEntry); InsertHeadList(&pdoExt->completedReadPacketsList, &readPacket->listEntry); satisfiedThisIrp = TRUE; break; } else if (readPacket->offset == readPacket->length){ /*
* We've depleted this readPacket buffer. */ InsertTailList(&readPacketsToFree, &readPacket->listEntry); ASSERT(!IsListEmpty(&pdoExt->completedReadPacketsList)); } else { DBGERR(("SatisfyPendingReads - data error")); break; }
}
ASSERT(satisfiedThisIrp); } else { DBGERR(("PosMmGetSystemAddressForMdlSafe failed")); irp->IoStatus.Information = 0; irp->IoStatus.Status = STATUS_UNSUCCESSFUL; InsertTailList(&irpsToCompleteList, &irp->Tail.Overlay.ListEntry); } } else { /*
* We can't satisfy this IRP, so put it back at the head of the list. */ NTSTATUS status; DBGVERBOSE(("SatisfyPendingReads: not enough bytes to satisfy irp (%xh/%xh)", pdoExt->totalQueuedReadDataLength, currentIrpSp->Parameters.Read.Length)); status = EnqueueReadIrp(pdoExt, irp, TRUE, TRUE); if (status == STATUS_CANCELLED){ /*
* The IRP was cancelled and the cancel routine was not called, * so complete the IRP here. */ irp->IoStatus.Information = 0; irp->IoStatus.Status = status; InsertTailList(&irpsToCompleteList, &irp->Tail.Overlay.ListEntry); }
break; }
}
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
while (!IsListEmpty(&irpsToCompleteList)){ listEntry = RemoveHeadList(&irpsToCompleteList); ASSERT(listEntry); irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry); DBGVERBOSE(("SatisfyPendingReads: completing irp with %xh bytes.", irp->IoStatus.Information));
/*
* Free the MDL we created for the UserBuffer */ ASSERT(irp->MdlAddress); MmUnlockPages(irp->MdlAddress); FREEPOOL(irp->MdlAddress); irp->MdlAddress = NULL;
IoCompleteRequest(irp, IO_NO_INCREMENT); }
while (!IsListEmpty(&readPacketsToFree)){ listEntry = RemoveHeadList(&readPacketsToFree); ASSERT(listEntry); readPacket = CONTAINING_RECORD(listEntry, READPACKET, listEntry); FreeReadPacket(readPacket); }
}
/*
* IssueReadForClient * * Must be called with exclusive access on the input endpoint held. */ VOID IssueReadForClient(POSPDOEXT *pdoExt) { PUCHAR readBuf; ULONG readLen;
DBGVERBOSE(("IssueReadForClient"));
/*
* We always read the full pipe size. * * BUGBUG - pipe info needs to be moved to pdoExt. */ readLen = pdoExt->inputEndpointInfo.pipeLen;
readBuf = ALLOCPOOL(NonPagedPool, readLen); if (readBuf){ READPACKET *readPacket;
RtlZeroMemory(readBuf, readLen);
readPacket = AllocReadPacket(readBuf, readLen, pdoExt); if (readPacket){ ReadPipe(pdoExt->parentFdoExt, pdoExt->inputEndpointInfo.pipeHandle, readPacket, FALSE); } else { FREEPOOL(readBuf); FlushBuffers(pdoExt); } } else { ASSERT(readBuf); FlushBuffers(pdoExt); }
}
VOID WorkItemCallback_Read(PVOID context) { POSPDOEXT *pdoExt = context; KIRQL oldIrql; BOOLEAN issueReadNow = FALSE;
DBGVERBOSE(("WorkItemCallback_Read"));
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
if (IsListEmpty(&pdoExt->pendingReadIrpsList)){ DBGERR(("WorkItemCallback_Read: list is empty ?!")); } else { if (pdoExt->inputEndpointInfo.endpointIsBusy){ DBGWARN(("WorkItemCallback_Read: endpoint is busy")); } else { pdoExt->inputEndpointInfo.endpointIsBusy = TRUE; issueReadNow = TRUE; } }
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
if (issueReadNow){ IssueReadForClient(pdoExt); }
}
NTSTATUS EnqueueReadIrp(POSPDOEXT *pdoExt, PIRP irp, BOOLEAN enqueueAtFront, BOOLEAN lockHeld) { PDRIVER_CANCEL oldCancelRoutine; NTSTATUS status = STATUS_PENDING; KIRQL oldIrql;
if (!lockHeld) KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
/*
* Enqueue the IRP */ if (enqueueAtFront){ InsertHeadList(&pdoExt->pendingReadIrpsList, &irp->Tail.Overlay.ListEntry); } else { InsertTailList(&pdoExt->pendingReadIrpsList, &irp->Tail.Overlay.ListEntry); }
/*
* Apply the IoMarkIrpPending macro to indicate that the * irp may complete on a different thread. * The kernel will see this flag set when we complete the IRP and get the IRP * back on the right thread if there was a synchronous client. */ IoMarkIrpPending(irp);
/*
* Must set the cancel routine before checking the cancel flag. */ oldCancelRoutine = IoSetCancelRoutine(irp, ReadCancelRoutine); ASSERT(!oldCancelRoutine);
if (irp->Cancel){ /*
* This IRP was cancelled. * We need to coordinate with the cancel routine to complete this irp. */ oldCancelRoutine = IoSetCancelRoutine(irp, NULL); if (oldCancelRoutine){ /*
* Cancel routine was not called, so dequeue the IRP and return * error so the dispatch routine completes the IRP. */ ASSERT(oldCancelRoutine == ReadCancelRoutine); RemoveEntryList(&irp->Tail.Overlay.ListEntry); status = STATUS_CANCELLED; } else { /*
* Cancel routine was called and it will complete the IRP * as soon as we drop the spinlock. So don't touch this IRP. * Return PENDING so dispatch routine won't complete the IRP. */ } }
if (!lockHeld) KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
return status; }
PIRP DequeueReadIrp(POSPDOEXT *pdoExt, BOOLEAN lockHeld) { PIRP nextIrp = NULL; KIRQL oldIrql;
if (!lockHeld) KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
while (!nextIrp && !IsListEmpty(&pdoExt->pendingReadIrpsList)){ PDRIVER_CANCEL oldCancelRoutine; PLIST_ENTRY listEntry = RemoveHeadList(&pdoExt->pendingReadIrpsList);
nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
oldCancelRoutine = IoSetCancelRoutine(nextIrp, NULL);
/*
* IoCancelIrp() could have just been called on this IRP. * What we're interested in is not whether IoCancelIrp() was called (nextIrp->Cancel flag set), * but whether IoCancelIrp() called (or is about to call) our cancel routine. * To check that, check the result of the test-and-set macro IoSetCancelRoutine. */ if (oldCancelRoutine){ /*
* Cancel routine not called for this IRP. Return this IRP. */ ASSERT(oldCancelRoutine == ReadCancelRoutine); } else { /*
* This IRP was just cancelled and the cancel routine was (or will be) called. * The cancel routine will complete this IRP as soon as we drop the spinlock. * So don't do anything with the IRP. * Also, the cancel routine will try to dequeue the IRP, * so make the IRP's listEntry point to itself. */ ASSERT(nextIrp->Cancel); InitializeListHead(&nextIrp->Tail.Overlay.ListEntry); nextIrp = NULL; } }
if (!lockHeld) KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
return nextIrp; }
VOID ReadCancelRoutine(PDEVICE_OBJECT devObj, PIRP irp) { DEVEXT *devExt; POSPDOEXT *pdoExt; KIRQL oldIrql;
DBGWARN(("ReadCancelRoutine: devObj=%ph, irp=%ph.", devObj, irp));
devExt = devObj->DeviceExtension; ASSERT(devExt->signature == DEVICE_EXTENSION_SIGNATURE); ASSERT(devExt->isPdo); pdoExt = &devExt->pdoExt;
IoReleaseCancelSpinLock(irp->CancelIrql);
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
RemoveEntryList(&irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(irp, IO_NO_INCREMENT); }
READPACKET *AllocReadPacket(PVOID data, ULONG dataLen, PVOID context) { READPACKET *readPacket;
readPacket = ALLOCPOOL(NonPagedPool, sizeof(READPACKET)); if (readPacket){ readPacket->signature = READPACKET_SIG; readPacket->data = data; readPacket->length = dataLen; readPacket->offset = 0; readPacket->context = context; readPacket->urb = BAD_POINTER; readPacket->listEntry.Flink = readPacket->listEntry.Blink = BAD_POINTER; } else { ASSERT(readPacket); }
return readPacket; }
VOID FreeReadPacket(READPACKET *readPacket) { DBGVERBOSE(("Freeing readPacket...")); ASSERT(readPacket->signature == READPACKET_SIG); FREEPOOL(readPacket->data); FREEPOOL(readPacket); }
|