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.
1319 lines
35 KiB
1319 lines
35 KiB
/*++
|
|
|
|
Copyright (C) 1992-9 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
print.c
|
|
|
|
Abstract:
|
|
|
|
The printer class driver tranlates IRPs to SRBs with embedded CDBs
|
|
and sends them to its devices through the port driver.
|
|
|
|
Author:
|
|
|
|
Mike Glass (mglass)
|
|
|
|
Environment:
|
|
|
|
kernel mode only
|
|
|
|
Notes:
|
|
|
|
Revision History:
|
|
|
|
georgioc - Made into a pnp class driver independent of the underlying storage bus
|
|
using the new storage/classpnp
|
|
|
|
dankn, 22-Jul-99 : Added ability to block & resubmit failed writes for
|
|
1394 printers to behave more like other print stacks
|
|
(i.e. USB) and therefore keep USBMON.DLL (the Win2k
|
|
port monitor) happy. USBMON does not deal well
|
|
with failed writes.
|
|
|
|
--*/
|
|
|
|
#include "printpnp.h"
|
|
#include "ntddser.h"
|
|
|
|
|
|
|
|
NTSTATUS
|
|
PrinterOpenClose(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to establish a connection to the printer
|
|
class driver. It does no more than return STATUS_SUCCESS.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Device object for a printer.
|
|
Irp - Open or Close request packet
|
|
|
|
Return Value:
|
|
|
|
NT Status - STATUS_SUCCESS
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Fdo->DeviceExtension;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
//
|
|
// Set status in Irp.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// forward irp.
|
|
//
|
|
|
|
ClassReleaseRemoveLock (Fdo, Irp);
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
return IoCallDriver(fdoExtension->CommonExtension.LowerDeviceObject, Irp);
|
|
|
|
} // end PrinterOpenClose()
|
|
|
|
|
|
NTSTATUS
|
|
BuildPrintRequest(
|
|
PDEVICE_OBJECT Fdo,
|
|
PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Build SRB and CDB requests to scsi printer.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Device object representing this printer device.
|
|
Irp - System IO request packet.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION deviceExtension = Fdo->DeviceExtension;
|
|
PIO_COMPLETION_ROUTINE completionRoutine;
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
PCDB cdb;
|
|
ULONG transferLength;
|
|
|
|
//
|
|
// Allocate Srb from nonpaged pool.
|
|
// This call must succeed.
|
|
//
|
|
|
|
srb = ExAllocatePool(NonPagedPool, SCSI_REQUEST_BLOCK_SIZE);
|
|
if (srb == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
srb->SrbFlags = 0;
|
|
|
|
//
|
|
// Write length to SRB.
|
|
//
|
|
|
|
srb->Length = SCSI_REQUEST_BLOCK_SIZE;
|
|
|
|
//
|
|
// Set up IRP Address.
|
|
//
|
|
|
|
srb->OriginalRequest = Irp;
|
|
|
|
//
|
|
// Set up target id and logical unit number.
|
|
//
|
|
|
|
srb->Function = SRB_FUNCTION_EXECUTE_SCSI;
|
|
|
|
srb->DataBuffer = MmGetMdlVirtualAddress(Irp->MdlAddress);
|
|
|
|
//
|
|
// Save byte count of transfer in SRB Extension.
|
|
//
|
|
|
|
srb->DataTransferLength = currentIrpStack->Parameters.Write.Length;
|
|
|
|
//
|
|
// Transfer length should never be greater than MAX_PRINT_XFER
|
|
//
|
|
|
|
ASSERT(srb->DataTransferLength <= MAX_PRINT_XFER);
|
|
|
|
//
|
|
// Initialize the queue actions field.
|
|
//
|
|
|
|
srb->QueueAction = SRB_SIMPLE_TAG_REQUEST;
|
|
|
|
//
|
|
// Queue sort key is not used.
|
|
//
|
|
|
|
srb->QueueSortKey = 0;
|
|
|
|
//
|
|
// Indicate auto request sense by specifying buffer and size.
|
|
//
|
|
|
|
srb->SenseInfoBuffer = deviceExtension->SenseData;
|
|
|
|
srb->SenseInfoBufferLength = SENSE_BUFFER_SIZE;
|
|
|
|
//
|
|
// Set timeout value in seconds.
|
|
//
|
|
|
|
srb->TimeOutValue = deviceExtension->TimeOutValue;
|
|
|
|
//
|
|
// Zero statuses.
|
|
//
|
|
|
|
srb->SrbStatus = srb->ScsiStatus = 0;
|
|
|
|
srb->NextSrb = 0;
|
|
|
|
//
|
|
// Get number of bytes to transfer.
|
|
//
|
|
|
|
transferLength = currentIrpStack->Parameters.Write.Length;
|
|
|
|
//
|
|
// Get pointer to CDB in SRB.
|
|
//
|
|
|
|
cdb = (PCDB) srb->Cdb;
|
|
|
|
//
|
|
// Init 10-byte READ CDB's for reads (per scanner device READ spec
|
|
// in SCSI-2), and 6-byte PRINT CDB's for writes
|
|
//
|
|
|
|
if (currentIrpStack->MajorFunction == IRP_MJ_READ) {
|
|
|
|
srb->CdbLength = 10;
|
|
srb->SrbFlags = SRB_FLAGS_DATA_IN;
|
|
|
|
RtlZeroMemory (cdb, 10);
|
|
|
|
cdb->CDB10.OperationCode = SCSIOP_READ;
|
|
|
|
//
|
|
// Move little endian values into CDB in big endian format.
|
|
//
|
|
|
|
cdb->CDB10.TransferBlocksLsb = ((PFOUR_BYTE) &transferLength)->Byte0;
|
|
cdb->CDB10.TransferBlocksMsb = ((PFOUR_BYTE) &transferLength)->Byte1;
|
|
cdb->CDB10.Reserved2 = ((PFOUR_BYTE) &transferLength)->Byte2;
|
|
|
|
//
|
|
// For read's we always use the ClassIoComplete completion routine
|
|
//
|
|
|
|
completionRoutine = ClassIoComplete;
|
|
|
|
} else {
|
|
|
|
srb->CdbLength = 6;
|
|
srb->SrbFlags = SRB_FLAGS_DATA_OUT;
|
|
|
|
cdb->PRINT.OperationCode = SCSIOP_PRINT;
|
|
cdb->PRINT.Reserved = 0;
|
|
cdb->PRINT.LogicalUnitNumber = 0;
|
|
|
|
//
|
|
// Move little endian values into CDB in big endian format.
|
|
//
|
|
|
|
cdb->PRINT.TransferLength[2] = ((PFOUR_BYTE) &transferLength)->Byte0;
|
|
cdb->PRINT.TransferLength[1] = ((PFOUR_BYTE) &transferLength)->Byte1;
|
|
cdb->PRINT.TransferLength[0] = ((PFOUR_BYTE) &transferLength)->Byte2;
|
|
|
|
cdb->PRINT.Control = 0;
|
|
|
|
//
|
|
// Set the appropriate write/print completion routine
|
|
//
|
|
|
|
completionRoutine = ((PPRINTER_DATA) deviceExtension->
|
|
CommonExtension.DriverData)->WriteCompletionRoutine;
|
|
}
|
|
|
|
//
|
|
// Or in the default flags from the device object.
|
|
//
|
|
|
|
srb->SrbFlags |= deviceExtension->SrbFlags;
|
|
|
|
//
|
|
// Set up major SCSI function.
|
|
//
|
|
|
|
nextIrpStack->MajorFunction = IRP_MJ_SCSI;
|
|
|
|
//
|
|
// Save SRB address in next stack for port driver.
|
|
//
|
|
|
|
nextIrpStack->Parameters.Scsi.Srb = srb;
|
|
|
|
//
|
|
// Save retry count in current IRP stack.
|
|
//
|
|
|
|
currentIrpStack->Parameters.Others.Argument4 = (PVOID)MAXIMUM_RETRIES;
|
|
|
|
//
|
|
// Set up IoCompletion routine address.
|
|
//
|
|
|
|
IoSetCompletionRoutine(Irp, completionRoutine, srb, TRUE, TRUE, TRUE);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // end BuildPrintRequest()
|
|
|
|
|
|
NTSTATUS
|
|
PrinterReadWrite(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the entry called by the I/O system for print requests.
|
|
It builds the SRB and sends it to the port driver.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - the system object for the device.
|
|
Irp - IRP involved.
|
|
|
|
Return Value:
|
|
|
|
NT Status
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION deviceExtension = Fdo->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
ULONG transferByteCount = currentIrpStack->Parameters.Write.Length;
|
|
ULONG maximumTransferLength;
|
|
ULONG transferPages;
|
|
NTSTATUS Status;
|
|
|
|
DEBUGPRINT3(("PrinterReadWrite: Enter routine\n"));
|
|
|
|
|
|
if (deviceExtension->AdapterDescriptor == NULL) {
|
|
|
|
//
|
|
// device removed..
|
|
//
|
|
|
|
DEBUGPRINT3(("PrinterReadWrite: Device removed(!!)\n"));
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
return STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
maximumTransferLength = deviceExtension->AdapterDescriptor->MaximumTransferLength;
|
|
|
|
|
|
//
|
|
// Calculate number of pages in this transfer.
|
|
//
|
|
|
|
transferPages = ADDRESS_AND_SIZE_TO_SPAN_PAGES(
|
|
MmGetMdlVirtualAddress(Irp->MdlAddress),
|
|
currentIrpStack->Parameters.Write.Length);
|
|
|
|
//
|
|
// Check if hardware maximum transfer length is larger than SCSI
|
|
// print command can handle. If so, lower the maximum allowed to
|
|
// the SCSI print maximum.
|
|
//
|
|
|
|
if (maximumTransferLength > MAX_PRINT_XFER)
|
|
maximumTransferLength = MAX_PRINT_XFER;
|
|
|
|
//
|
|
// Check if request length is greater than the maximum number of
|
|
// bytes that the hardware can transfer.
|
|
//
|
|
|
|
if (currentIrpStack->Parameters.Write.Length > maximumTransferLength ||
|
|
transferPages > deviceExtension->AdapterDescriptor->MaximumPhysicalPages) {
|
|
|
|
transferPages =
|
|
deviceExtension->AdapterDescriptor->MaximumPhysicalPages - 1;
|
|
|
|
if (maximumTransferLength > transferPages << PAGE_SHIFT ) {
|
|
maximumTransferLength = transferPages << PAGE_SHIFT;
|
|
}
|
|
|
|
//
|
|
// Check that maximum transfer size is not zero.
|
|
//
|
|
|
|
if (maximumTransferLength == 0) {
|
|
maximumTransferLength = PAGE_SIZE;
|
|
}
|
|
|
|
//
|
|
// Mark IRP with status pending.
|
|
//
|
|
|
|
IoMarkIrpPending(Irp);
|
|
|
|
//
|
|
// Request greater than port driver maximum.
|
|
// Break up into smaller routines.
|
|
//
|
|
|
|
SplitRequest(Fdo,
|
|
Irp,
|
|
maximumTransferLength);
|
|
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
//
|
|
// Build SRB and CDB for this IRP.
|
|
//
|
|
|
|
Status = BuildPrintRequest(Fdo, Irp);
|
|
if (!NT_SUCCESS (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Return the results of the call to the port driver.
|
|
//
|
|
|
|
return IoCallDriver(deviceExtension->CommonExtension.LowerDeviceObject, Irp);
|
|
|
|
} // end ScsiPrinterWrite()
|
|
|
|
|
|
NTSTATUS
|
|
PrinterDeviceControl(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the NT device control handler for Printers.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - for this Printer
|
|
|
|
Irp - IO Request packet
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFUNCTIONAL_DEVICE_EXTENSION deviceExtension = Fdo->DeviceExtension;
|
|
|
|
|
|
|
|
DEBUGPRINT2 (("PrinterDeviceControl: enter, Fdo=x%p, Ioctl=", Fdo));
|
|
|
|
//
|
|
// Zero CDB in SRB on stack.
|
|
//
|
|
|
|
switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
|
|
|
|
case IOCTL_SERIAL_SET_TIMEOUTS: {
|
|
|
|
PSERIAL_TIMEOUTS newTimeouts = ((PSERIAL_TIMEOUTS) buffer);
|
|
|
|
|
|
DEBUGPRINT2 (("SET_TIMEOUTS\n"));
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(SERIAL_TIMEOUTS)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else if (newTimeouts->WriteTotalTimeoutConstant < 2000) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
deviceExtension->TimeOutValue =
|
|
newTimeouts->WriteTotalTimeoutConstant / 1000;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_TIMEOUTS:
|
|
|
|
DEBUGPRINT2(("GET_TIMEOUTS\n"));
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(SERIAL_TIMEOUTS)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else {
|
|
|
|
RtlZeroMemory (buffer, sizeof (SERIAL_TIMEOUTS));
|
|
|
|
Irp->IoStatus.Information = sizeof(SERIAL_TIMEOUTS);
|
|
|
|
((PSERIAL_TIMEOUTS) buffer)->WriteTotalTimeoutConstant =
|
|
deviceExtension->TimeOutValue * 1000;
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
|
|
case IOCTL_USBPRINT_GET_LPT_STATUS:
|
|
|
|
//
|
|
// We support this ioctl for USBMON.DLL's sake. Other print
|
|
// stacks will block failed writes, and eventually USBMON
|
|
// will send them this ioctl to see if the printer is out
|
|
// of paper, which will be indicated by the state of the
|
|
// 0x20 bit in the returned UCHAR value.
|
|
//
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.OutputBufferLength <
|
|
sizeof(UCHAR)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else if (deviceExtension->AdapterDescriptor->BusType !=
|
|
BusType1394) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
PPRINTER_DATA printerData;
|
|
|
|
|
|
printerData = (PPRINTER_DATA)
|
|
deviceExtension->CommonExtension.DriverData;
|
|
|
|
Irp->IoStatus.Information = sizeof (UCHAR);
|
|
|
|
*((UCHAR *) buffer) = (printerData->LastWriteStatus ==
|
|
STATUS_NO_MEDIA_IN_DEVICE ? 0x20 : 0);
|
|
|
|
DEBUGPRINT2((
|
|
"GET_LPT_STATUS (=x%x)\n",
|
|
(ULONG) *((UCHAR *) buffer)
|
|
));
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
|
|
case IOCTL_SCSIPRNT_1394_BLOCKING_WRITE:
|
|
|
|
//
|
|
// This ioctl en/disables the blocking write functionality
|
|
// (for failed writes) on 1394 devices. By default we
|
|
// block writes which fail on 1394 devices (until the write
|
|
// finally succeeds or is cancelled), but a smart port
|
|
// monitor could send this ioctl down to disable blocking
|
|
// so it would get write error notifications asap.
|
|
//
|
|
|
|
if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
|
|
sizeof(UCHAR)) {
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
} else if (deviceExtension->AdapterDescriptor->BusType !=
|
|
BusType1394) {
|
|
|
|
status = STATUS_INVALID_PARAMETER;
|
|
|
|
} else {
|
|
|
|
PPRINTER_DATA printerData;
|
|
|
|
|
|
printerData = (PPRINTER_DATA)
|
|
deviceExtension->CommonExtension.DriverData;
|
|
|
|
printerData->WriteCompletionRoutine = (*((UCHAR *) buffer) ?
|
|
PrinterWriteComplete : ClassIoComplete);
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Pass the request to the common device control routine.
|
|
//
|
|
|
|
DEBUGPRINT2((
|
|
"x%x\n",
|
|
irpStack->Parameters.DeviceIoControl.IoControlCode
|
|
));
|
|
|
|
return(ClassDeviceControl(Fdo, Irp));
|
|
|
|
break;
|
|
|
|
} // end switch()
|
|
|
|
//
|
|
// Update IRP with completion status.
|
|
//
|
|
|
|
Irp->IoStatus.Status = status;
|
|
|
|
//
|
|
// Complete the request.
|
|
//
|
|
|
|
IoCompleteRequest(Irp, IO_DISK_INCREMENT);
|
|
|
|
//
|
|
// Release the remove lock (which ClassDeviceControl does)
|
|
//
|
|
|
|
ClassReleaseRemoveLock(Fdo, Irp);
|
|
|
|
DEBUGPRINT2(( "PrinterDeviceControl: Status is %lx\n", status));
|
|
return status;
|
|
|
|
} // end ScsiPrinterDeviceControl()
|
|
|
|
|
|
|
|
VOID
|
|
SplitRequest(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp,
|
|
IN ULONG MaximumBytes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Break request into smaller requests. Each new request will be the
|
|
maximum transfer size that the port driver can handle or if it
|
|
is the final request, it may be the residual size.
|
|
|
|
The number of IRPs required to process this request is written in the
|
|
current stack of the original IRP. Then as each new IRP completes
|
|
the count in the original IRP is decremented. When the count goes to
|
|
zero, the original IRP is completed.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Pointer to the class device object to be addressed.
|
|
|
|
Irp - Pointer to Irp the orginal request.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION deviceExtension = Fdo->DeviceExtension;
|
|
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
|
|
ULONG transferByteCount = currentIrpStack->Parameters.Read.Length;
|
|
LARGE_INTEGER startingOffset = currentIrpStack->Parameters.Read.ByteOffset;
|
|
PVOID dataBuffer = MmGetMdlVirtualAddress(Irp->MdlAddress);
|
|
ULONG dataLength = MaximumBytes;
|
|
ULONG irpCount = (transferByteCount + MaximumBytes - 1) / MaximumBytes;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
ULONG i;
|
|
NTSTATUS Status;
|
|
|
|
DEBUGPRINT2(( "SplitRequest: Requires %d IRPs\n", irpCount));
|
|
DEBUGPRINT2(( "SplitRequest: Original IRP %p\n", Irp));
|
|
|
|
//
|
|
// If all partial transfers complete successfully then the status and
|
|
// bytes transferred are already set up. Failing a partial-transfer IRP
|
|
// will set status to error and bytes transferred to 0 during
|
|
// IoCompletion. Setting bytes transferred to 0 if an IRP fails allows
|
|
// asynchronous partial transfers. This is an optimization for the
|
|
// successful case.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = transferByteCount;
|
|
|
|
//
|
|
// Save number of IRPs to complete count on current stack
|
|
// of original IRP.
|
|
//
|
|
|
|
nextIrpStack->Parameters.Others.Argument1 = ULongToPtr( irpCount );
|
|
|
|
for (i = 0; i < irpCount; i++) {
|
|
|
|
PIRP newIrp;
|
|
PIO_STACK_LOCATION newIrpStack;
|
|
|
|
//
|
|
// Allocate new IRP.
|
|
//
|
|
|
|
newIrp = IoAllocateIrp(Fdo->StackSize, FALSE);
|
|
|
|
if (newIrp == NULL) {
|
|
|
|
DEBUGPRINT1(("SplitRequest: Can't allocate Irp\n"));
|
|
|
|
//
|
|
// If an Irp can't be allocated then the orginal request cannot
|
|
// be executed. If this is the first request then just fail the
|
|
// orginal request; otherwise just return. When the pending
|
|
// requests complete, they will complete the original request.
|
|
// In either case set the IRP status to failure.
|
|
//
|
|
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if (i == 0) {
|
|
|
|
IoCompleteRequest (Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DEBUGPRINT2(( "SplitRequest: New IRP %p\n", newIrp));
|
|
|
|
//
|
|
// Write MDL address to new IRP. In the port driver the SRB data
|
|
// buffer field is used as an offset into the MDL, so the same MDL
|
|
// can be used for each partial transfer. This saves having to build
|
|
// a new MDL for each partial transfer.
|
|
//
|
|
|
|
newIrp->MdlAddress = Irp->MdlAddress;
|
|
|
|
//
|
|
// At this point there is no current stack. IoSetNextIrpStackLocation
|
|
// will make the first stack location the current stack so that the
|
|
// SRB address can be written there.
|
|
//
|
|
|
|
IoSetNextIrpStackLocation(newIrp);
|
|
newIrpStack = IoGetCurrentIrpStackLocation(newIrp);
|
|
|
|
newIrpStack->MajorFunction = currentIrpStack->MajorFunction;
|
|
newIrpStack->Parameters.Read.Length = dataLength;
|
|
newIrpStack->Parameters.Read.ByteOffset = startingOffset;
|
|
newIrpStack->DeviceObject = Fdo;
|
|
|
|
//
|
|
// Build SRB and CDB.
|
|
//
|
|
|
|
Status = BuildPrintRequest(Fdo, newIrp);
|
|
if (!NT_SUCCESS (Status)) {
|
|
IoFreeIrp (newIrp);
|
|
|
|
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if (i == 0) {
|
|
|
|
IoCompleteRequest (Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Adjust SRB for this partial transfer.
|
|
//
|
|
|
|
newIrpStack = IoGetNextIrpStackLocation(newIrp);
|
|
|
|
srb = newIrpStack->Parameters.Others.Argument1;
|
|
srb->DataBuffer = dataBuffer;
|
|
|
|
//
|
|
// Write original IRP address to new IRP.
|
|
//
|
|
|
|
newIrp->AssociatedIrp.MasterIrp = Irp;
|
|
|
|
//
|
|
// Set the completion routine to ScsiClassIoCompleteAssociated.
|
|
//
|
|
|
|
IoSetCompletionRoutine(newIrp,
|
|
ClassIoCompleteAssociated,
|
|
srb,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
//
|
|
// Call port driver with new request.
|
|
//
|
|
|
|
IoCallDriver(deviceExtension->CommonExtension.LowerDeviceObject, newIrp);
|
|
|
|
//
|
|
// Set up for next request.
|
|
//
|
|
|
|
dataBuffer = (PCHAR)dataBuffer + MaximumBytes;
|
|
|
|
transferByteCount -= MaximumBytes;
|
|
|
|
if (transferByteCount > MaximumBytes) {
|
|
|
|
dataLength = MaximumBytes;
|
|
|
|
} else {
|
|
|
|
dataLength = transferByteCount;
|
|
}
|
|
|
|
//
|
|
// Adjust disk byte offset.
|
|
//
|
|
|
|
startingOffset.QuadPart = startingOffset.QuadPart + MaximumBytes;
|
|
}
|
|
|
|
return;
|
|
|
|
} // end SplitRequest()
|
|
|
|
|
|
|
|
NTSTATUS
|
|
PrinterWriteComplete(
|
|
IN PDEVICE_OBJECT Fdo,
|
|
IN PIRP Irp,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Ideally we should be should be able to use ClassIoComplete for
|
|
all write completion notifications, but alas...
|
|
|
|
(Code borrowed from classpnp!ClassIoComplete)
|
|
|
|
This is the special, 1394 bus-specific write completion routine
|
|
required to keep USBMON.DLL happy in the case of failed write
|
|
requests. The other stacks that USBMON talks to all pend
|
|
unsuccessful writes forever, rather than simply completing them
|
|
with an error. When a write blocks for a long time USBMON will
|
|
issue a sideband ioctl, namely IOCTL_USBPRINT_GET_LPT_STATUS,
|
|
to determine if the printer is out of paper or not. Eventually
|
|
USBMON may cancel a blocked write. However, it simply doesn't
|
|
expect writes to just fail, so we have to fake out the behavior
|
|
of the other stacks to keep it happy. We'll retry blocked
|
|
writes every so often, & mark the irp as cancellable in between
|
|
retries.
|
|
|
|
At least USBMON will only send down one 10k (or so) write at a
|
|
time, so we don't have to worry about queue-ing >1 write at a
|
|
time for a device, nor do we have to deal with handling failed
|
|
sub-requests of a split write.
|
|
|
|
Arguments:
|
|
|
|
Fdo - Supplies the device object which represents the logical unit.
|
|
|
|
Irp - Supplies the Irp which has completed.
|
|
|
|
Context - Supplies a pointer to the SRB.
|
|
|
|
Return Value:
|
|
|
|
NT status
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG retryInterval;
|
|
KIRQL oldIrql;
|
|
BOOLEAN retry;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation (Irp);
|
|
PSCSI_REQUEST_BLOCK srb = Context;
|
|
PCOMMON_DEVICE_EXTENSION extension = Fdo->DeviceExtension;
|
|
NTSTATUS status;
|
|
|
|
ASSERT(extension->IsFdo);
|
|
|
|
if (extension->IsRemoved == NO_REMOVE){
|
|
|
|
if (SRB_STATUS(srb->SrbStatus) == SRB_STATUS_SUCCESS){
|
|
/*
|
|
* Call ClassIoComplete to free the SRB, release the remove lock, and propagate the pending bit;
|
|
* then let the irp continue completing.
|
|
*/
|
|
status = ClassIoComplete(Fdo, Irp, Context);
|
|
}
|
|
else {
|
|
PPRINTER_DATA printerData = (PPRINTER_DATA)extension->DriverData;
|
|
|
|
//
|
|
// Note that ClassInterpretSenseInfo will return (retry=)
|
|
// FALSE if it determines there's no media in device
|
|
//
|
|
retry = ClassInterpretSenseInfo(
|
|
Fdo,
|
|
srb,
|
|
irpStack->MajorFunction,
|
|
0,
|
|
MAXIMUM_RETRIES - ((ULONG)(ULONG_PTR)
|
|
irpStack->Parameters.Others.Argument4),
|
|
&printerData->LastWriteStatus,
|
|
&retryInterval
|
|
);
|
|
|
|
if (retry && ((ULONG)(ULONG_PTR)irpStack->Parameters.Others.Argument4)--){
|
|
PrinterRetryRequest (Fdo, Irp, srb);
|
|
status = STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else {
|
|
BOOLEAN queuedWriteIrp;
|
|
|
|
if (printerData->LastWriteStatus == STATUS_NO_MEDIA_IN_DEVICE) {
|
|
|
|
//
|
|
// At the current time Epson is returning
|
|
// SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED for both
|
|
// the out-of-paper & offline cases. The EndOfMedia
|
|
// bit wil be set if the printer is truly out of paper,
|
|
// but if it's not then we want to change the
|
|
// LastWriteStatus so that we won't set the out-of-paper
|
|
// bit in the IOCTL_USBPRINT_GET_LPT_STATUS handler.
|
|
//
|
|
|
|
PSENSE_DATA senseBuffer = srb->SenseInfoBuffer;
|
|
|
|
|
|
if ((senseBuffer->AdditionalSenseCodeQualifier == SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED) &&
|
|
!senseBuffer->EndOfMedia) {
|
|
|
|
printerData->LastWriteStatus = STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
}
|
|
|
|
printerData->DueTime.HighPart = -1;
|
|
printerData->DueTime.LowPart = BLOCKED_WRITE_TIMEOUT * (-10 * 1000 * 1000);
|
|
|
|
queuedWriteIrp = SetWriteIrp(printerData, Irp, srb);
|
|
if (queuedWriteIrp){
|
|
KeSetTimer(&printerData->Timer, printerData->DueTime, &printerData->TimerDpc);
|
|
status = STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
else {
|
|
/*
|
|
* There is already a WriteIrp set, so we have to complete this one.
|
|
* Call ClassIoComplete to free the SRB, release the remove lock, and propagate the pending bit;
|
|
* then let the irp continue completing.
|
|
*/
|
|
status = ClassIoComplete(Fdo, Irp, Context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Call ClassIoComplete to free the SRB, release the remove lock, and propagate the pending bit;
|
|
* then let the irp continue completing.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
Irp->IoStatus.Information = 0;
|
|
status = ClassIoComplete(Fdo, Irp, Context);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
PrinterRetryRequest(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp,
|
|
PSCSI_REQUEST_BLOCK Srb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
(Code borrowed from classpnp!ClassIoComplete, since we need to
|
|
set a different completion routine)
|
|
|
|
This routine reinitalizes the necessary fields, and sends the request
|
|
to the lower driver.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Supplies the device object associated with this request.
|
|
|
|
Irp - Supplies the request to be retried.
|
|
|
|
Srb - Supplies a Pointer to the SCSI request block to be retied.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG transferByteCount;
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(Irp);
|
|
PCOMMON_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension;
|
|
|
|
|
|
//
|
|
// Determine the transfer count of the request. If this is a read or a
|
|
// write then the transfer count is in the Irp stack. Otherwise assume
|
|
// the MDL contains the correct length. If there is no MDL then the
|
|
// transfer length must be zero.
|
|
//
|
|
|
|
if (currentIrpStack->MajorFunction == IRP_MJ_READ ||
|
|
currentIrpStack->MajorFunction == IRP_MJ_WRITE) {
|
|
|
|
transferByteCount = currentIrpStack->Parameters.Read.Length;
|
|
|
|
} else if (Irp->MdlAddress != NULL) {
|
|
|
|
//
|
|
// Note this assumes that only read and write requests are spilt and
|
|
// other request do not need to be. If the data buffer address in
|
|
// the MDL and the SRB don't match then transfer length is most
|
|
// likely incorrect.
|
|
//
|
|
|
|
ASSERT(Srb->DataBuffer == MmGetMdlVirtualAddress(Irp->MdlAddress));
|
|
transferByteCount = Irp->MdlAddress->ByteCount;
|
|
|
|
} else {
|
|
|
|
transferByteCount = 0;
|
|
}
|
|
|
|
//
|
|
// Reset byte count of transfer in SRB Extension.
|
|
//
|
|
|
|
Srb->DataTransferLength = transferByteCount;
|
|
|
|
//
|
|
// Zero SRB statuses.
|
|
//
|
|
|
|
Srb->SrbStatus = Srb->ScsiStatus = 0;
|
|
|
|
//
|
|
// Set the no disconnect flag, disable synchronous data transfers and
|
|
// disable tagged queuing. This fixes some errors.
|
|
//
|
|
|
|
Srb->SrbFlags |= SRB_FLAGS_DISABLE_DISCONNECT |
|
|
SRB_FLAGS_DISABLE_SYNCH_TRANSFER;
|
|
|
|
Srb->SrbFlags &= ~SRB_FLAGS_QUEUE_ACTION_ENABLE;
|
|
Srb->QueueTag = SP_UNTAGGED;
|
|
|
|
//
|
|
// Set up major SCSI function.
|
|
//
|
|
|
|
nextIrpStack->MajorFunction = IRP_MJ_SCSI;
|
|
|
|
//
|
|
// Save SRB address in next stack for port driver.
|
|
//
|
|
|
|
nextIrpStack->Parameters.Scsi.Srb = Srb;
|
|
|
|
//
|
|
// Set up IoCompletion routine address.
|
|
//
|
|
|
|
IoSetCompletionRoutine (Irp, PrinterWriteComplete, Srb, TRUE, TRUE, TRUE);
|
|
|
|
//
|
|
// Pass the request to the port driver.
|
|
//
|
|
|
|
IoCallDriver (extension->LowerDeviceObject, Irp);
|
|
|
|
return;
|
|
} // end PrinterRetryRequest()
|
|
|
|
|
|
|
|
VOID
|
|
PrinterWriteTimeoutDpc(
|
|
IN PKDPC Dpc,
|
|
IN PVOID Context,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets called when the blocking-write timer expires. Allocates &
|
|
queues a low-priority work item (to resubmit the write) if
|
|
there's an outstanding write, & if the allocation fails justs
|
|
resets the time to try again later. (We're running at raised
|
|
irql here, when it's not necesarily safe to re-submit the write,
|
|
hence the work item which gets processed later at passive level.)
|
|
|
|
Arguments:
|
|
|
|
Dpc -
|
|
|
|
Extension -
|
|
|
|
SystemArgument1 -
|
|
|
|
SystemArgument2 -
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PFUNCTIONAL_DEVICE_EXTENSION fdoExtension = Context;
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = &fdoExtension->CommonExtension;
|
|
PPRINTER_DATA printerData = commonExtension->DriverData;
|
|
PIRP writeIrp;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
|
|
if (commonExtension->IsRemoved == NO_REMOVE){
|
|
|
|
if (printerData->WriteIrp){
|
|
PIO_WORKITEM workItem;
|
|
|
|
workItem = IoAllocateWorkItem(commonExtension->DeviceObject);
|
|
if (workItem) {
|
|
IoQueueWorkItem(workItem, PrinterResubmitWrite, DelayedWorkQueue, workItem);
|
|
}
|
|
else {
|
|
writeIrp = GetWriteIrp(printerData, &srb);
|
|
if (writeIrp){
|
|
ExFreePool(srb);
|
|
writeIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
writeIrp->IoStatus.Information = 0;
|
|
ClassReleaseRemoveLock(commonExtension->DeviceObject, writeIrp);
|
|
if (writeIrp->PendingReturned){
|
|
IoMarkIrpPending(writeIrp);
|
|
}
|
|
IoCompleteRequest(writeIrp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* We're being removed. Don't issue the workItem.
|
|
* If we have a queued writeIrp, complete the irp and free the srb.
|
|
*/
|
|
writeIrp = GetWriteIrp(printerData, &srb);
|
|
if (writeIrp){
|
|
ExFreePool(srb);
|
|
writeIrp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
writeIrp->IoStatus.Information = 0;
|
|
ClassReleaseRemoveLock(commonExtension->DeviceObject, writeIrp);
|
|
if (writeIrp->PendingReturned){
|
|
IoMarkIrpPending(writeIrp);
|
|
}
|
|
IoCompleteRequest(writeIrp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
PrinterResubmitWrite(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Work item handler routine, gets called at passive level in an
|
|
arbitrary thread context. Simply resubmits an outstanding write,
|
|
if any.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject -
|
|
|
|
Context - pointer to the IO_WORKITEM
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PCOMMON_DEVICE_EXTENSION commonExtension = DeviceObject->DeviceExtension;
|
|
PPRINTER_DATA printerData = (PPRINTER_DATA)commonExtension->DriverData;
|
|
PIRP writeIrp;
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
|
|
IoFreeWorkItem ((PIO_WORKITEM)Context);
|
|
|
|
writeIrp = GetWriteIrp(printerData, &srb);
|
|
if (writeIrp){
|
|
|
|
ExFreePool(srb);
|
|
|
|
if (commonExtension->IsRemoved == NO_REMOVE){
|
|
PrinterReadWrite(DeviceObject, writeIrp);
|
|
}
|
|
else {
|
|
writeIrp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
writeIrp->IoStatus.Information = 0;
|
|
ClassReleaseRemoveLock(DeviceObject, writeIrp);
|
|
if (writeIrp->PendingReturned){
|
|
IoMarkIrpPending(writeIrp);
|
|
}
|
|
IoCompleteRequest(writeIrp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
|
|
} // end PrinterWriteTimeoutDpc
|
|
|
|
|
|
BOOLEAN SetWriteIrp(PPRINTER_DATA PrinterData, PIRP WriteIrp, PSCSI_REQUEST_BLOCK Srb)
|
|
{
|
|
BOOLEAN didSet;
|
|
KIRQL oldIrql;
|
|
|
|
KeAcquireSpinLock(&PrinterData->SplitRequestSpinLock, &oldIrql);
|
|
|
|
if (!PrinterData->WriteIrp){
|
|
/*
|
|
* This is not perfect irp queuing with cancellation,
|
|
* but it works here since the irp will not be cancelled before we receive it.
|
|
* Since we're queuing the irp, it may complete on a different thread than it was issues on; so set the pending bit.
|
|
*/
|
|
ASSERT(!PrinterData->WriteSrb);
|
|
IoMarkIrpPending(WriteIrp);
|
|
PrinterData->WriteIrp = WriteIrp;
|
|
PrinterData->WriteSrb = Srb;
|
|
didSet = TRUE;
|
|
}
|
|
else if (PrinterData->WriteIrp == WriteIrp){
|
|
/*
|
|
* This can happen on retry (??)
|
|
*/
|
|
ASSERT(PrinterData->WriteSrb == Srb);
|
|
didSet = TRUE;
|
|
}
|
|
else {
|
|
didSet = FALSE;
|
|
}
|
|
|
|
KeReleaseSpinLock(&PrinterData->SplitRequestSpinLock, oldIrql);
|
|
|
|
return didSet;
|
|
}
|
|
|
|
|
|
PIRP GetWriteIrp(PPRINTER_DATA PrinterData, PSCSI_REQUEST_BLOCK *Srb)
|
|
{
|
|
PIRP writeIrp;
|
|
KIRQL oldIrql;
|
|
|
|
KeAcquireSpinLock(&PrinterData->SplitRequestSpinLock, &oldIrql);
|
|
|
|
if (PrinterData->WriteIrp){
|
|
writeIrp = PrinterData->WriteIrp;
|
|
PrinterData->WriteIrp = NULL;
|
|
|
|
ASSERT(PrinterData->WriteSrb);
|
|
*Srb = PrinterData->WriteSrb;
|
|
PrinterData->WriteSrb = NULL;
|
|
}
|
|
else {
|
|
writeIrp = NULL;
|
|
ASSERT(!PrinterData->WriteSrb);
|
|
*Srb = NULL;
|
|
}
|
|
|
|
KeReleaseSpinLock(&PrinterData->SplitRequestSpinLock, oldIrql);
|
|
|
|
return writeIrp;
|
|
}
|
|
|