Hi,
This is a query about an old but very useful OSR article “Properly Pending IRPs - IRP Handling for the Rest of Us” (http://www.osronline.com/article.cfm?id=21) and hope this is a right forum.
My query is about rules 2 & 3 described in the article:
Rule 2: If you return from your Dispatch Routine without completing the IRP passed to you, you must (a) Mark the IRP pending, before the IRP is completed to your caller, by calling IoMarkIrpPending(), and (b) return STATUS_PENDING from your Dispatch Routine.
Rule 3: If you return the status from the driver below yours as the status from your dispatch routine, the IRP?s pending status, as indicated by Irp->PendingReturned, must be propagated with a completion routine.
My doubt is about this contradiction: Rule (2) says you must return STAUTS_PENDING if you don’t complete the IRP in dispatch, whereas Rule (3) suggests the possibility of returning the status from driver below ‘as it is’ which may not be STATUS_PENDING.
So is it really okay to return ANY status from dispatch (as suggested in Rule 3), or we must return STATUS_PENDING (as mentioned in Rule 2)? Thanks.
Regards,
Suresh
Rule 2 is a case when an IRP is being made pending by your driver without passing it to a lower driver or completing it in Dispatch Routine. Your driver “owns” this IRP and holds it until it decides to continue with this IRP( complete or pass to a lower driver ).
The dispatch for Rule 2 looks like
MyDispatchRule2( Device, Irp )
{
…
IoMarkIrpPending( Irp );
InsertIrpInInternalQueue( Irp ); // save the IRP pointer for future or asynchronous processing
return STATUS_PENDING;
}
Rule 3 is a case when you passed an IRP to a lower driver by calling IoCallDriver, thus you have lost the IRP “ownership” until your completion routine is being called. A lower driver in the stack might complete this IRP or make it pending.
MyDispatchRule3( Device, Irp )
{
…
IoCopyCurrentIrpStackLocationToNext( Irp );
IoSetCompletionRoutine( Irp, MyCompletionRule3, … );
return IoCallDriver( LowerDevice, Irp );
}
MyCompletionRule3()
{
…
if( Irp->PendingReturned )
IoMarkIrpPending( Irp );
return STATUS_SUCCESS;
}
Actually, you are allowed to complete or pass an IRP and return STATUS_PENDING( with a call to IoMarkIrpPending ) from Dispatch Routine, but this is a degenerate case. So you can combine Rule 2 and Rule 3 and a Dispatch Routine will be
MyDispatchRule2AndRule3Combined( Device, Irp )
{
…
IoCopyCurrentIrpStackLocationToNext( Irp );
IoSetCompletionRoutine( Irp, MyCompletionRule3, … );
IoMarkIrpPending( Irp );
IoCallDriver( LowerDevice, Irp );
// here you have lost the IRP “ownership”, do not touch the IRP after IoCallDriver unless you synchronize with completion routine
return STATUS_PENDING;
}
the IO Manager( or upper driver ) will synchronize the IRP completion (if required) despite STATUS_PENDING being returned
Thanks for a clear and detailed explanation!
About ‘MyDispatchRule2AndRule3Combined’ though, I think IoMarkIrpPending can be avoided as long as MyCompletionRule3 implements:
if( Irp->PendingReturned )
IoMarkIrpPending( Irp );
Is that right?
You still have to call IoMarkIrpPending as Irp->PendingReturned is set by IoCompleteRequest by carrying over the SL_PENDING_RETURNED flag set by IoMarkIrpPending to Irp->PendingReturned bool value in the process of IRP’s “stack locations unwinding”. Failure to do this might result in an incorrect completion sequence for IRP.
Well, my conclusion was based on the following lines from the OSR article that I mentioned above:
“There?s an important subtlety to understand about Rule 2. Note that it says you must mark the IRP pending ?before the IRP is completed to your caller?. This shouldn?t be interpreted to mean you must to mark the IRP pending before you return from your Dispatch Routine. After all, no driver (including the driver above you) has access to an IRP from its Dispatch Routine after returning from IoCallDriver(). So, as long as you mark the IRP pending before the IRP gets completed, everything will be fine.”
The IRP is going to be completed to the caller only from my completion routine where I am anyway checking for PendingReturned and calling IoMarkIrpPending if found so.
Or, are you suggesting that we call IoMarkIrpPending in dispatch before calling IoCallDriver and then it is NOT necessary to check for PendingReturned flag in completion?
The IRP is not completed from your completion routine because
- If your completion routine is called the IRP has been completed by a lower driver in the stack by calling IoCompleteRequest.
- If you don’t pass IRP to a lower driver and call IoCompleteRequest your completion routine is not going to be called at all.
IoMarkIrpPending marks the current stack location with SL_PENDING_RETURNED flag, you are not going to see Irp->PendingReturned if there is no SL_PENDING_RETURNED flag in a lower stack location. That means it is your driver’s responsibility to set SL_PENDING_RETURNED flag to signal pending condition to an upper driver/caller and you must do this in both Dispatch Routine when STATUS_PENDING is returned and in completion routine to propagate the flag over IRP’s stack locations.
The ultimate rule is:
STATUS_PENDING returned by a dispatch routine MUST match SL_PENDING_RETURNED at its stack location. All code variations are simply enforcing this rule.
Thanks Slava and Alex, but I am just finding it difficult to disregard this statement in the article:
“This shouldn’t be interpreted to mean that you must mark the IRP pending before
you return from your Dispatch Routine”.
IoMarkIrpPending marks the current stack location with SL_PENDING_RETURNED flag,
you are not going to see Irp->PendingReturned if there is no SL_PENDING_RETURNED
flag in a lower stack location. That means it is your driver’s responsibility to
set SL_PENDING_RETURNED flag.
If there is no SL_PENDING_RETURNED flag in a lower stack location then that means the lower driver completed the IRP synchronously and so will you, which means there is no need for you set SL_PENDING_RETURNED flag in your own stack location. Isn’t it?
STATUS_PENDING is checked by I/O manager when the topmost IoCallDriver returns.
SL_PENDING_RETURNED is checked by the topmost completion routine (IoCompleteRequest tail) to see if a special completion APC needs to be invoked.
These two conditions need to be in sync, to make sure the completion tail (copying the data and IO status to the user buffer) is invoked once and only once.
SL_PENDING_RETURNED has to be valid only during execution of the completion chain. It doesn’t have to be valid when STATUS_PENDING is returned. But their ultimate values need to match.
Thanks Alex, this makes it crystal clear!
To summarize:
-
The rule 2 which says that your dispatch must return STATUS_PENDING if you are not completing an IRP in dispatch is only applicable to the case when an IRP is being made pending by your driver without passing it to a lower driver (as pointed out by Slava). May be it will be good to mention this in the article.
-
If you decide to return STATUS_PENDING from your dispatch then you MUST call IoMarkIrpPending in your dispatch before passing the IRP down (and now I see why Slava has done so in MyDispatchRule2AndRule3Combined).
-
If you decide to return the status of the driver below you in the dispatch then you SHOULD NOT call IoMarkIrpPending in your dispatch, but rather should check PendingReturned flag in your completion and call IoMarkIrpPending if it is set.
@Suresh:
If you decide to return STATUS_PENDING from your dispatch then you MUST call
IoMarkIrpPending in your dispatch before passing the IRP down
Not exactly. The stack location only has to be marked pending before your completion routine returns. You could call IoMarkIrpPending in your dispatch routine, or in your completion routine.
> Not exactly. The stack location only has to be marked pending before your
completion routine returns. You could call IoMarkIrpPending in your dispatch
routine, or in your completion routine.
Well, if you always return STATUS_PENDING (like MyDispatchRule2AndRule3Combined above does) by NOT marking Irp as pending in the dispatch, and if the lower driver completes the request synchronously then PendingReturned flag will not be set and so you will end up NOT marking the Irp as pending in your completion routine too.
This will break the rule:
STATUS_PENDING returned by a dispatch routine MUST match SL_PENDING_RETURNED at
its stack location.
@Suresh:
Well, if you always return STATUS_PENDING (like MyDispatchRule2AndRule3Combined
above does) by NOT marking Irp as pending in the dispatch, and if the lower
driver completes the request synchronously then PendingReturned flag will not be
set and so you will end up NOT marking the Irp as pending in your completion
routine too
If you always return STATUS_PENDING from your dispatch routine, then you can unconditionally do IoMarkIrpPending in your completion routine.
One thing not mentioned here:
If you use IoSkipCurrentIrpStackLocation, You MUST NOT call IoMarkIrpPending, and you MUST return the result of IoCallDriver. You cannot return STATUS_PENDING in this case.
> If you always return STATUS_PENDING from your dispatch routine, then you can
unconditionally do IoMarkIrpPending in your completion routine.
Oh, got it! Thanks.
Btw I was reading this related MSFT article: Different ways of handling IRPs (https://support.microsoft.com/en-us/kb/320275)
In that under Scenario 3 it is mentioned that:
“If you return the status of the lower driver from your dispatch routine, you must not change the status of the IRP in the completion routine.”
Does that mean that if there is a need for me to change the status of the IRP on its way up, I should always return STATUS_PENDING from my dispatch?
>Does that mean that if there is a need for me to change the status of the IRP on
its way up, I should always return STATUS_PENDING from my dispatch?
The requirement is only that the IRP status seen by the completion routine above your driver is the same as completion status returned by your dispatch routine (unless you return STATUS_PENDING).
> The requirement is only that the IRP status seen by the completion routine above
your driver is the same as completion status returned by your dispatch routine
(unless you return STATUS_PENDING).
Got it, thanks.
So for the case where I may need to change the IRP status in my completion routine, I have only these 2 options:
-
Always return STATUS_PENDING from your dispatch, (therefore) always mark the IRP pending in your completion, update the IRP status in your completion (if needed) and return STATUS_CONTINUE_COMPLETION.
-
Process the IRP synchronously, i.e. in your dispatch wait for an even that will be set by your completion (which returns STATUS_MORE_PROCESSING_REQUIRED) and then call IoCompleteRequest by ensuring the return status from dispatch and IRP status are same.
Yes, that’s correct.
One thing to not forget. In case 2 you must NOT use IoMarkIrpPending in the completion routine (if (Irp->PendingReturned) IoMarkIrpPending(Irp)