mirror of https://github.com/tongzx/nt5src
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.
1430 lines
34 KiB
1430 lines
34 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;
|
|
PPRINTER_DATA printerData;
|
|
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation (Irp);
|
|
PSCSI_REQUEST_BLOCK srb = Context;
|
|
|
|
PCOMMON_DEVICE_EXTENSION extension = Fdo->DeviceExtension;
|
|
|
|
|
|
ASSERT(extension->IsFdo);
|
|
|
|
if (SRB_STATUS(srb->SrbStatus) != SRB_STATUS_SUCCESS) {
|
|
|
|
DEBUGPRINT3 (("PrinterWriteCompl: Fdo=x%p, Irp=x%p, ", Fdo, Irp));
|
|
|
|
if (((PFUNCTIONAL_DEVICE_EXTENSION) extension)->AdapterDescriptor ==
|
|
NULL) {
|
|
|
|
//
|
|
// Device removed..
|
|
//
|
|
|
|
DEBUGPRINT3(("device removed(\n"));
|
|
|
|
Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
ClassReleaseRemoveLock (Fdo, Irp);
|
|
|
|
return STATUS_DEVICE_DOES_NOT_EXIST;
|
|
}
|
|
|
|
if (Irp->Cancel) {
|
|
|
|
//
|
|
// Someone tried to cancel the irp after it was passed
|
|
// down the stack (where, as of win2k, there is no cancel
|
|
// support), so bail out now
|
|
//
|
|
|
|
DEBUGPRINT3 (("irp cancelled\n"));
|
|
|
|
Irp->IoStatus.Status = STATUS_CANCELLED;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
ClassReleaseRemoveLock (Fdo, Irp);
|
|
|
|
return STATUS_CANCELLED;
|
|
}
|
|
|
|
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)--) {
|
|
|
|
DEBUGPRINT3 (("retry write\n"));
|
|
|
|
PrinterRetryRequest (Fdo, Irp, srb);
|
|
|
|
} else {
|
|
|
|
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);
|
|
|
|
KeAcquireSpinLock (&printerData->SplitRequestSpinLock, &oldIrql);
|
|
|
|
ASSERT (printerData->WriteIrp == NULL);
|
|
|
|
if (printerData->WriteIrp == NULL ||
|
|
printerData->WriteIrp == Irp) {
|
|
|
|
printerData->WriteIrp = Irp;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We assume that if we're in blocking write mode then
|
|
// the client is USBMON who will only submit 1 write
|
|
// at a time (other clients should use
|
|
// IOCTL_SCSIPRNT_1394_BLOCKING_WRITE to disable blocking
|
|
// writes if they don't want this behavior).
|
|
//
|
|
// Since we don't handle >1 blocked write at a time we'll
|
|
// just complete this 2nd write, so we don't lose track of
|
|
// (and fail to complete) any irps.
|
|
//
|
|
|
|
KeReleaseSpinLock (&printerData->SplitRequestSpinLock, oldIrql);
|
|
|
|
return ClassIoComplete (Fdo, Irp, Context);
|
|
}
|
|
|
|
KeReleaseSpinLock (&printerData->SplitRequestSpinLock, oldIrql);
|
|
|
|
IoSetCancelRoutine (Irp, PrinterCancel);
|
|
|
|
KeSetTimer(
|
|
&printerData->Timer,
|
|
printerData->DueTime,
|
|
&printerData->TimerDpc
|
|
);
|
|
|
|
DEBUGPRINT3 ((
|
|
"Sts=x%x, pend write\n",
|
|
printerData->LastWriteStatus
|
|
));
|
|
}
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
return ClassIoComplete (Fdo, Irp, Context);
|
|
|
|
} // end PrinterWriteComplete()
|
|
|
|
|
|
|
|
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 PCOMMON_DEVICE_EXTENSION Extension,
|
|
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
|
|
|
|
--*/
|
|
|
|
{
|
|
PIO_WORKITEM workItem;
|
|
PPRINTER_DATA printerData = (PPRINTER_DATA) Extension->DriverData;
|
|
|
|
|
|
DEBUGPRINT3((
|
|
"PrinterWriteTimeoutDpc: enter, Fdo=x%p, Irp=x%p\n",
|
|
Extension->DeviceObject,
|
|
printerData->WriteIrp
|
|
));
|
|
|
|
if (printerData->WriteIrp) {
|
|
|
|
workItem = IoAllocateWorkItem (Extension->DeviceObject);
|
|
|
|
if (workItem) {
|
|
|
|
IoQueueWorkItem(
|
|
workItem,
|
|
PrinterResubmitWrite,
|
|
DelayedWorkQueue, // not time critical
|
|
workItem
|
|
);
|
|
|
|
} else {
|
|
|
|
printerData->DueTime.HighPart = -1;
|
|
printerData->DueTime.LowPart =
|
|
BLOCKED_WRITE_TIMEOUT * (-10 * 1000 * 1000);
|
|
|
|
KeSetTimer(
|
|
&printerData->Timer,
|
|
printerData->DueTime,
|
|
&printerData->TimerDpc
|
|
);
|
|
}
|
|
}
|
|
|
|
} // end PrinterWriteTimeoutDpc
|
|
|
|
|
|
|
|
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
|
|
|
|
--*/
|
|
|
|
{
|
|
PIRP irp;
|
|
KIRQL oldIrql;
|
|
PPRINTER_DATA printerData;
|
|
|
|
|
|
DEBUGPRINT3 (("PrinterResubmitWrite: enter, Fdo=x%p, ", DeviceObject));
|
|
|
|
IoFreeWorkItem ((PIO_WORKITEM) Context);
|
|
|
|
printerData = (PPRINTER_DATA)
|
|
((PCOMMON_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->DriverData;
|
|
|
|
|
|
|
|
//
|
|
// See if there's still an outstanding write irp, & if so NULL-ify
|
|
// the cancel routine since we'll be passing the irp down the stack
|
|
//
|
|
|
|
KeAcquireSpinLock (&printerData->SplitRequestSpinLock, &oldIrql);
|
|
|
|
irp = printerData->WriteIrp;
|
|
|
|
if (irp) {
|
|
|
|
if (IoSetCancelRoutine (irp, NULL) == NULL) {
|
|
|
|
DEBUGPRINT3 (("write cancelled\n"));
|
|
|
|
irp = NULL;
|
|
|
|
} else {
|
|
|
|
printerData->WriteIrp = NULL;
|
|
}
|
|
|
|
} else {
|
|
|
|
DEBUGPRINT3 (("no pending write\n"));
|
|
}
|
|
|
|
KeReleaseSpinLock (&printerData->SplitRequestSpinLock, oldIrql);
|
|
|
|
|
|
//
|
|
// Rebsubmit an outstanding write irp
|
|
//
|
|
|
|
if (irp) {
|
|
|
|
PFUNCTIONAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension;
|
|
|
|
|
|
if (extension->AdapterDescriptor) {
|
|
|
|
DEBUGPRINT3 (("Irp=x%p\n", irp));
|
|
|
|
PrinterReadWrite (DeviceObject, irp);
|
|
|
|
} else {
|
|
|
|
DEBUGPRINT3((" RMV!, fail Irp=x%p\n", irp));
|
|
|
|
IoMarkIrpPending (irp);
|
|
|
|
irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST;
|
|
irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest (irp, IO_NO_INCREMENT);
|
|
|
|
ClassReleaseRemoveLock (DeviceObject, irp);
|
|
}
|
|
}
|
|
|
|
} // end PrinterWriteTimeoutDpc
|
|
|
|
|
|
|
|
VOID
|
|
PrinterCancel(
|
|
PDEVICE_OBJECT DeviceObject,
|
|
PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cancels a blocked write irp
|
|
|
|
Arguments:
|
|
|
|
DeviceObject -
|
|
|
|
Irp - irp to cancel
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL oldIrql;
|
|
PPRINTER_DATA printerData;
|
|
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
|
|
PSCSI_REQUEST_BLOCK srb;
|
|
|
|
|
|
DEBUGPRINT2((
|
|
"\nPrinterCancel: enter, DevObj=x%p, Irp=x%p\n\n",
|
|
DeviceObject,
|
|
Irp
|
|
));
|
|
|
|
printerData = (PPRINTER_DATA)
|
|
((PCOMMON_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->DriverData;
|
|
|
|
IoReleaseCancelSpinLock (Irp->CancelIrql);
|
|
|
|
KeCancelTimer (&printerData->Timer);
|
|
|
|
KeAcquireSpinLock (&printerData->SplitRequestSpinLock, &oldIrql);
|
|
|
|
ASSERT (Irp == printerData->WriteIrp);
|
|
|
|
printerData->WriteIrp = NULL;
|
|
|
|
KeReleaseSpinLock (&printerData->SplitRequestSpinLock, oldIrql);
|
|
|
|
// see if this is a SCSI Irp we sent down
|
|
|
|
if(currentIrpStack->MajorFunction == IRP_MJ_SCSI)
|
|
{
|
|
// the associated SRB never got freed, so do it before we complete this request
|
|
|
|
srb = currentIrpStack->Parameters.Scsi.Srb;
|
|
|
|
if (srb)
|
|
{
|
|
currentIrpStack->Parameters.Scsi.Srb = NULL;
|
|
ExFreePool(srb);
|
|
}
|
|
}
|
|
|
|
Irp->IoStatus.Status = STATUS_CANCELLED;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest (Irp, IO_NO_INCREMENT);
|
|
|
|
} // end PrinterCancel
|