/*++ 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 #include #include #include #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); }