Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

303 lines
8.3 KiB

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name:
write.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 WriteComPort(POSPDOEXT *pdoExt, PIRP irp)
{
NTSTATUS status;
PIO_STACK_LOCATION currentIrpSp;
/*
* In order to support ODD ENDPOINTs, we check
* whether this COM port has a write endpoint or not.
*/
if(!pdoExt->outputEndpointInfo.pipeHandle) {
DBGVERBOSE(("This PORT does not have an OUT endpoint - Write request Rejected."));
return STATUS_NOT_SUPPORTED;
}
currentIrpSp = IoGetCurrentIrpStackLocation(irp);
/*
* Because this pdo's buffering method is METHOD_NEITHER,
* the buffer pointer is irp->UserBuffer, which may be an application address.
* Since we may not be able to send this buffer synchronously on this thread,
* we have to allocate an MDL for it.
*/
ASSERT(!irp->MdlAddress);
ASSERT(currentIrpSp->Parameters.Write.Length);
irp->MdlAddress = MmCreateMdl(NULL, irp->UserBuffer, currentIrpSp->Parameters.Write.Length);
if (irp->MdlAddress){
status = STATUS_SUCCESS;
__try {
/*
* We're reading the write data from the buffer, so probe for ReadAccess.
*/
MmProbeAndLockPages(irp->MdlAddress, UserMode, IoReadAccess);
}
__except(EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
DBGERR(("MmProbeAndLockPages triggered exception status %xh.", status));
}
if (NT_SUCCESS(status)){
status = TryWrite(pdoExt, irp);
}
}
else {
DBGERR(("MmCreateMdl failed"));
status = STATUS_DATA_ERROR;
}
return status;
}
NTSTATUS TryWrite(POSPDOEXT *pdoExt, PIRP irp)
{
NTSTATUS status = STATUS_PENDING;
BOOLEAN isBusy;
KIRQL oldIrql;
BOOLEAN irpWasCancelled = FALSE;
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
if (pdoExt->outputEndpointInfo.endpointIsBusy){
/*
* Another thread is writing to this endpoint now.
* Queue the IRP.
*/
PDRIVER_CANCEL oldCancelRoutine;
DBGWARN(("WriteComPort: endpoint is busy so queuing irp"));
oldCancelRoutine = IoSetCancelRoutine(irp, WriteCancelRoutine);
ASSERT(!oldCancelRoutine);
if (irp->Cancel){
DBGWARN(("WriteComPort: irp %ph was cancelled.", irp));
irpWasCancelled = TRUE;
oldCancelRoutine = IoSetCancelRoutine(irp, NULL);
if (oldCancelRoutine){
/*
* Cancel routine was not called, so complete the IRP here.
*/
ASSERT(oldCancelRoutine == ReadCancelRoutine);
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.
*/
status = STATUS_PENDING;
}
}
else {
InsertTailList(&pdoExt->pendingWriteIrpsList, &irp->Tail.Overlay.ListEntry);
}
isBusy = TRUE;
}
else {
pdoExt->outputEndpointInfo.endpointIsBusy = TRUE;
isBusy = FALSE;
}
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
if (!isBusy){
PUCHAR mappedUserBuffer;
ULONG dataLen;
ULONG dataWritten = 0;
PIO_STACK_LOCATION currentIrpSp;
BOOLEAN callWriteWorkItem = FALSE;
currentIrpSp = IoGetCurrentIrpStackLocation(irp);
status = STATUS_SUCCESS; // in case dataLen = 0
/*
* This function is called from the dispatch routine and also
* from a workItem callback. So we may not be on the calling thread.
* Therefore, we cannot use irp->UserBuffer because it may not be
* mapped in this context. Use the MDL we created at call time instead.
*/
mappedUserBuffer = PosMmGetSystemAddressForMdlSafe(irp->MdlAddress);
if(mappedUserBuffer) {
dataLen = currentIrpSp->Parameters.Write.Length;
while (dataLen){
ULONG len = MIN(dataLen, pdoExt->outputEndpointInfo.pipeLen);
DBGVERBOSE(("Writing %xh bytes to pipe.", len));
status = WritePipe( pdoExt->parentFdoExt,
pdoExt->outputEndpointInfo.pipeHandle,
mappedUserBuffer,
len);
if (NT_SUCCESS(status)){
dataLen -= len;
dataWritten += len;
mappedUserBuffer += len;
}
else {
DBGERR(("Write failed with status %xh.", status));
break;
}
}
/*
* Free the MDL we created for the UserBuffer
*/
ASSERT(irp->MdlAddress);
MmUnlockPages(irp->MdlAddress);
FREEPOOL(irp->MdlAddress);
irp->MdlAddress = NULL;
irp->IoStatus.Information = dataWritten;
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
pdoExt->outputEndpointInfo.endpointIsBusy = FALSE;
if (!IsListEmpty(&pdoExt->pendingWriteIrpsList)){
callWriteWorkItem = TRUE;;
}
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
/*
* If there are some writes waiting, schedule a workItem to process them.
*/
if (callWriteWorkItem){
ExQueueWorkItem(&pdoExt->writeWorkItem, DelayedWorkQueue);
}
}
else {
/*
* Return STATUS_UNSUCCESSFUL and free the MDL we created for the UserBuffer.
*/
DBGERR(("PosMmGetSystemAddressForMdlSafe failed"));
irp->IoStatus.Information = 0;
status = STATUS_UNSUCCESSFUL;
ASSERT(irp->MdlAddress);
MmUnlockPages(irp->MdlAddress);
FREEPOOL(irp->MdlAddress);
irp->MdlAddress = NULL;
}
}
return status;
}
VOID WorkItemCallback_Write(PVOID context)
{
POSPDOEXT *pdoExt = (POSPDOEXT *)context;
KIRQL oldIrql;
PIRP irp = NULL;
DBGVERBOSE(("WorkItemCallback_Write: pdoExt=%ph ", pdoExt));
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
if (IsListEmpty(&pdoExt->pendingWriteIrpsList)){
DBGERR(("WorkItemCallback_Write: list is empty ?!"));
}
else {
while (!irp && !IsListEmpty(&pdoExt->pendingWriteIrpsList)){
PDRIVER_CANCEL cancelRoutine;
PLIST_ENTRY listEntry = RemoveHeadList(&pdoExt->pendingWriteIrpsList);
ASSERT(listEntry);
irp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
cancelRoutine = IoSetCancelRoutine(irp, NULL);
if (cancelRoutine){
ASSERT(cancelRoutine == WriteCancelRoutine);
}
else {
/*
* This IRP was cancelled and the cancel routine was called.
* The cancel routine will complete this IRP as soon as we drop
* the spinlock, so don't touch the IRP.
*/
ASSERT(irp->Cancel);
DBGWARN(("WorkItemCallback_Write: irp was cancelled"));
irp = NULL;
}
}
}
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
if (irp){
NTSTATUS status = TryWrite(pdoExt, irp);
if (status != STATUS_PENDING){
/*
* Set the SL_PENDING in the IRP
* to indicate that the IRP is completing on a different thread.
*/
IoMarkIrpPending(irp);
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}
}
}
VOID WriteCancelRoutine(PDEVICE_OBJECT devObj, PIRP irp)
{
DEVEXT *devExt;
POSPDOEXT *pdoExt;
KIRQL oldIrql;
DBGWARN(("WriteCancelRoutine: devObj=%ph, irp=%ph.", devObj, irp));
devExt = devObj->DeviceExtension;
ASSERT(devExt->signature == DEVICE_EXTENSION_SIGNATURE);
ASSERT(devExt->isPdo);
pdoExt = &devExt->pdoExt;
KeAcquireSpinLock(&pdoExt->devExtSpinLock, &oldIrql);
RemoveEntryList(&irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&pdoExt->devExtSpinLock, oldIrql);
IoReleaseCancelSpinLock(irp->CancelIrql);
irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(irp, IO_NO_INCREMENT);
}