Kmdf USB completion routine claims context is pageable

I have a KMDF USB driver for my serial device, and an extremely simple completion routine for when I transmit data to that device. I'm trying to limit the number of pending transmit requests to an arbitrary number (in this case, 10, but not very relevant).

So I have a 'port' context that I've successfully used everywhere else in my code, including in other completion routines for other actions (such as sending a request to read a specific register value). I have added a "writes_in_flight" integer to this port context, and I have written some very simple code to increase the number of writes when I send a request and decrease it when the completion routine finishes. Everywhere else in the code I can modify, assign, or print writes_in_flight or other variables in the port context. In another completion routine, I even use "pending_frame_size_reads" to track an annoying implementation I have to do so I can sort received data into the appropriate chunks.
In my completion routine for transmitting data, however, I get a bugcheck for "DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)".

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 000019faed905e44, memory referenced
Arg2: 0000000000000002, IRQL
Arg3: 0000000000000000, value 0 = read operation, 1 = write operation
Arg4: fffff80133d87206, address which referenced memory

Now I know the above isn't terribly useful, but I can also provide the code it has a problem with. This is literally the entire function right now, copied and pasted in its entirety:

void tx_completion(_In_ WDFREQUEST Request, _In_ WDFIOTARGET Target, _In_ PWDF_REQUEST_COMPLETION_PARAMS CompletionParams, _In_ WDFCONTEXT Context)
{
	struct synccom_port* port = 0;
	NTSTATUS    status;
	PWDF_USB_REQUEST_COMPLETION_PARAMS usb_completion_params;
	//int x = 0; // TEST
	
	//x = 5; // TEST

	UNREFERENCED_PARAMETER(Target);

	port = (PSYNCCOM_PORT)Context;
	if (!port) {
		WdfObjectDelete(Request);
		return;
	}
	usb_completion_params = CompletionParams->Parameters.Usb.Completion;
	status = CompletionParams->IoStatus.Status;
	if (!NT_SUCCESS(status)) {
		TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE, "%s: Read failed: Request status: 0x%x, UsbdStatus: 0x%x\n", __FUNCTION__, status, usb_completion_params->UsbdStatus);
		WdfObjectDelete(Request);
		return;
	}

	//DbgPrint(DEVICE_NAME "synccom writes_in_flight = %d\n", port->writes_in_flight); // IRQL
	//port->writes_in_flight = 5; // IRQL
	//port->writes_in_flight = port->writes_in_flight - 1; // IRQL
	//port->writes_in_flight++; // IRQL
	//port->writes_in_flight = x; // IRQL
	// change from INT to UINT32? // IRQL
	if (port->writes_in_flight > 0) port->writes_in_flight--; // IRQL
	
	WdfObjectDelete(Request);
	return;
}

And just in case it's relevant:

typedef struct synccom_port {
// .. a lot of variables
	BOOLEAN append_status;
	BOOLEAN append_timestamp;
	BOOLEAN ignore_timeout;
	BOOLEAN rx_multiple;
	int pending_frame_size_reads;
	int tx_modifiers;
	int writes_in_flight;
// .. a lot of other variables

} SYNCCOM_PORT, * PSYNCCOM_PORT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(SYNCCOM_PORT, GetPortContext)

I know it looks like I'm just throwing code at the wall, and that's because I am. I refused to accept that the port context is pageable or somehow changing an integer can't be done at a certain IRQL. It feels like I'm fundamentally misunderstanding or missing something, and I'm pulling my hair out at this point.

I would greatly appreciate some guidance, if anyone can offer it. Nothing I do works, or makes any real sense.

I'm going to leave this here as a warning to anyone who has no one to rubber duck to.

Type it out, read it out loud a couple times, spend 10 minutes thinking before you post.

I have resolved my issue, and it is now obvious in hindsight. The reason the port context was so broken is because I wasn't passing in the port context when setting up the completion routine. I was accidentally passing in the pipe context. You cannot access a variable that simply does not exist.

1 Like

You are not the first. The act of desperation involved in posting for help here apparently clears the mind and allows you to see your own code as it is, not as you think it ought to be.

1 Like

Glad you solved it!

IRQL_NOT_LESS_THAN_OR_EQUAL can be a red herring if you forget the fine print of the error code:

An attempt was made to access a pageable (or completely invalid) address at an interrupt request level (IRQL) that is too high.

So, yes, it happens if you touch paged out, pageable memory at IRQL >= DISPATCH_LEVEL. But the truth is that it happens for ANY page fault at IRQL >= DISPATCH_LEVEL and pageable memory is just by way of example but not limitation.