Hi,
I am in the process of debugging a usb driver that has a System Page Table Entry leak. The driver is perfectly functional (until it runs out of PTEs which takes about 1/2 day). I have traced the problem to be inside a Windows function, so I suspect I am setting something up slightly wrong. Hopefully someone can offer me advice on where to go from here.
BTW to examine the allocated System Page Table Entries I use “!sysptes 1” .
The problem starts in the DispatchReadWrite() routine (for reads). What happens is that DispatchReadWrite() routine calls a completion routine (ReadWriteCompletion()) via IoCallDriver(). Just before IoCallDriver() is executed no PTEs have been consumed. When IoCallDriver() is stepped into it leads to ReadWriteCompletion() but it consumes a PTE *before* reaching ReadWriteCompletion().
When the code gets into the completion routine, because the transfer size is greater than the MAX_TRANSFER_SIZE, the completion routine makes a recursive call to itself (again using IOCallDriver() ). Again as before the call to IOCallDriver() causes a PTE to be consumed.
On the second pass through ReadWriteCompletion() the number of bytes left to transfer is equal to the MAX_TRANSFER_SIZE, so this is the final pass through this function. On this second (and final) pass through, ReadWriteCompletion() follows a path that includes the function IoFreeMdl(). When this function is executed, it frees up one PTE. (Although when the IoAllocateMDL() function was called at the start of DispatchReadWrite(), no PTEs were consumed)
And that pretty much is it. Two PTEs are consumed, and only one is freed each time through the dispatch/completion routines. Below I have included the code of the two functions - with error checking on paths that are never exected taken out.
Any ideas on what I am doing wrong??
NTSTATUS BulkUsb_DispatchReadWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PMDL mdl;
PURB urb;
ULONG totalLength;
ULONG stageLength;
ULONG urbFlags;
BOOLEAN read;
NTSTATUS ntStatus;
ULONG_PTR virtualAddress;
PFILE_OBJECT fileObject;
PDEVICE_EXTENSION deviceExtension;
PIO_STACK_LOCATION irpStack;
PIO_STACK_LOCATION nextStack;
PBULKUSB_RW_CONTEXT rwContext;
PUSBD_PIPE_INFORMATION pipeInformation;
//
// initialize variables
//
urb = NULL;
mdl = NULL;
rwContext = NULL;
totalLength = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
fileObject = irpStack->FileObject;
read = (irpStack->MajorFunction == IRP_MJ_READ) ? TRUE : FALSE;
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
if(deviceExtension->SSEnable) {
KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent,
Executive,
KernelMode,
FALSE,
NULL);
}
pipeInformation = fileObject->FsContext;
rwContext = (PBULKUSB_RW_CONTEXT)
ExAllocatePoolWithTag(NonPagedPool,
sizeof(BULKUSB_RW_CONTEXT), 0x11111111);
if(Irp->MdlAddress) {
totalLength = MmGetMdlByteCount(Irp->MdlAddress);
}
urbFlags = USBD_SHORT_TRANSFER_OK;
virtualAddress = (ULONG_PTR) MmGetMdlVirtualAddress(Irp->MdlAddress);
if(read) {
urbFlags |= USBD_TRANSFER_DIRECTION_IN;
}
else {
urbFlags |= USBD_TRANSFER_DIRECTION_OUT;
}
//
// the transfer request is for totalLength.
// we can perform a max of BULKUSB_MAX_TRANSFER_SIZE
// in each stage.
//
if(totalLength > BULKUSB_MAX_TRANSFER_SIZE) {
stageLength = BULKUSB_MAX_TRANSFER_SIZE;
}
else {
stageLength = totalLength;
}
mdl = IoAllocateMdl((PVOID) virtualAddress,
totalLength,
FALSE,
FALSE,
NULL);
//
// map the portion of user-buffer described by an mdl to another mdl
//
IoBuildPartialMdl(Irp->MdlAddress,
mdl,
(PVOID) virtualAddress,
stageLength);
urb = ExAllocatePoolWithTag(NonPagedPool,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER), 0x22222222);
UsbBuildInterruptOrBulkTransferRequest(
urb,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
pipeInformation->PipeHandle,
NULL,
mdl,
stageLength,
urbFlags,
NULL);
rwContext->Urb = urb;
rwContext->Mdl = mdl;
rwContext->Length = totalLength - stageLength;
rwContext->Numxfer = 0;
rwContext->VirtualAddress = virtualAddress + stageLength;
rwContext->DeviceExtension = deviceExtension;
//
// use the original read/write irp as an internal device control irp
//
nextStack = IoGetNextIrpStackLocation(Irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.Others.Argument1 = (PVOID) urb;
nextStack->Parameters.DeviceIoControl.IoControlCode =
IOCTL_INTERNAL_USB_SUBMIT_URB;
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE)BulkUsb_ReadWriteCompletion,
rwContext,
TRUE,
TRUE,
TRUE);
//
// since we return STATUS_PENDING call IoMarkIrpPending.
// This is the boiler plate code.
// This may cause extra overhead of an APC for the Irp completion
// but this is the correct thing to do.
//
IoMarkIrpPending(Irp);
BulkUsb_IoIncrement(deviceExtension);
ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,
Irp); <------------------ consumes 1 PTE
//
// we return STATUS_PENDING and not the status returned by the lower layer.
//
return STATUS_PENDING;
}
NTSTATUS BulkUsb_ReadWriteCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
/*++
Routine Description:
This is the completion routine for reads/writes
If the irp completes with success, we check if we
need to recirculate this irp for another stage of
transfer. In this case return STATUS_MORE_PROCESSING_REQUIRED.
if the irp completes in error, free all memory allocs and
return the status.
–*/
{
ULONG stageLength;
NTSTATUS ntStatus;
PIO_STACK_LOCATION nextStack;
PBULKUSB_RW_CONTEXT rwContext;
//
// initialize variables
//
rwContext = (PBULKUSB_RW_CONTEXT) Context;
ntStatus = Irp->IoStatus.Status;
UNREFERENCED_PARAMETER(DeviceObject);
//
// successfully performed a stageLength of transfer.
// check if we need to recirculate the irp.
//
if(NT_SUCCESS(ntStatus)) {
if(rwContext) {
rwContext->Numxfer +=
rwContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength;
if(rwContext->Length) {
//
// another stage transfer
//
if(rwContext->Length > BULKUSB_MAX_TRANSFER_SIZE) {
stageLength = BULKUSB_MAX_TRANSFER_SIZE;
}
else {
stageLength = rwContext->Length;
}
IoBuildPartialMdl(Irp->MdlAddress,
rwContext->Mdl,
(PVOID) rwContext->VirtualAddress,
stageLength);
//
// reinitialize the urb
//
rwContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength
= stageLength;
rwContext->VirtualAddress += stageLength;
rwContext->Length -= stageLength;
nextStack = IoGetNextIrpStackLocation(Irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.Others.Argument1 = rwContext->Urb;
nextStack->Parameters.DeviceIoControl.IoControlCode =
IOCTL_INTERNAL_USB_SUBMIT_URB;
IoSetCompletionRoutine(Irp,
BulkUsb_ReadWriteCompletion,
rwContext,
TRUE,
TRUE,
TRUE);
IoCallDriver(rwContext->DeviceExtension->TopOfStackDeviceObject,
Irp); <------------------ consumes 1 PTE
return STATUS_MORE_PROCESSING_REQUIRED;
}
else {
//
// this is the last transfer
//
Irp->IoStatus.Information = rwContext->Numxfer;
}
}
}
else {
BulkUsb_DbgPrint(1, (“ReadWriteCompletion - failed with status = %X\n”, ntStatus));
}
if(rwContext) {
BulkUsb_IoDecrement(rwContext->DeviceExtension);
ExFreePoolWithTag(rwContext->Urb, 0x22222222);
IoFreeMdl(rwContext->Mdl); <------------------ frees 1 PTE
ExFreePoolWithTag(rwContext, 0x11111111);
}
return ntStatus;
}
Dr David Braendler