Completion routines during SystemSetPower

I’m trying to more or less implement https://community.osr.com/discussion/51051 except for the system power irp case.

  1. Receive a DispatchPower call
  2. Add a completion routine and pass IRP down (all documentation says I can’t block)
  3. Completion routine gets called, allocates a new write IRP, sets a second completion routine, then sends it down
  4. When the write IRP completes, its completion routine will complete the power irp.

I’ve been reading docs all week long and I can’t figure out what’s wrong.

NSTATUS TestDispatchPower(IN PDEVICE_OBJECT, IN PIRP) 
{
    PDEVICE_EXTENSION pDeviceExtension;
    PIO_STACK_LOCATION pIoStack = IoGetCurrentIrpStackLocation(pIrp);
    //NTSTATUS ntStatus = STATUS_SUCCESS;

    pDeviceExtension = (PDEVICE_EXTENSION) pDeviceObject->DeviceExtension;
      
    if (pIoStack->MinorFunction == IRP_MN_SET_POWER &&
            pIoStack->Parameters.Power.Type == SystemPowerState &&
            pIoStack->Parameters.Power.State.SystemState == PowerSystemShutdown) )
    {
        pDeviceExtension->pPowerOffIrp = pIrp;
      
        IoCopyCurrentIrpStackLocationToNext(pIrp);
        IoSetCompletionRoutine(pIrp, PowerOffIrpCompletionTest, pDeviceExtension, TRUE, TRUE, TRUE); 
        IoCallDriver(pDeviceExtension->TargetDeviceObject, pIrp);
        return STATUS_PENDING;
    }
    ...
    // defaults
} 

NTSTATUS
    PowerOffIrpCompletionTest(
    IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP pIrp,
    IN PVOID pContext
    )
{
    
    PDEVICE_EXTENSION pDeviceExtension = (PDEVICE_EXTENSION) pDeviceObject->DeviceExtension;
    PIRP pWriteIrp = NULL;

    if(pIrp->PendingReturned) {
        IoMarkIrpPending(pIrp);
    }

    pWriteIrp = CreateWriteIrp(/*magic*/);
    
    IoSetCompletionRoutine(pWriteIrp, BitmapWrite_Completion,
        pContext, TRUE, TRUE, TRUE);   
    IoCallDriver(pDeviceExtension->TargetDeviceObject,
        pWriteIrp);
    
    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS
    BitmapWrite_Completion(
    IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP pIrp,
    IN PVOID pContext
    )
{
    PDEVICE_EXTENSION pDeviceExtension = (PDEVICE_EXTENSION) pContext;
    UNREFERENCED_PARAMETER(pDeviceObject);

    //cleanup write IRP
    DestroyWriteIrp(pIrp, TRUE);

    // finish power off IRP
    IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, NULL);
    pDeviceExtension->pPowerOffIrp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(pDeviceExtension->pPowerOffIrp, IO_NO_INCREMENT);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

Is there anything fundamentally wrong with this design? Right now it hangs on shutdown. No bluescreen code, just a hang.

  1. you don’t show assiging pPowerOffIrp in PowerOffIrpCompletionTest, are you capturing the power irp?
  2. you don’t need to change the status of the power irp when you complete it as you could be changing a failure to a success

the bigger problem is that by the type the set power irp completes, the underlying device could be powered off. A better approach would be to pend the set power irp, send the write, when the write completes, send the power irp down the stack (fire and forget, no need for a completion routine).

d

In addition to what Doron said, you need to call IoMarkIrpPending in TestDispatchPower before the call to IoCallDriver. As written the code only properly manages the pending bit if the lower driver processes the IRP asychronously.

(All hail WDF for mostly making all this IRP handling esoterica go away…)

@Doron_Holan said:

  1. you don’t show assiging pPowerOffIrp in PowerOffIrpCompletionTest, are you capturing the power irp?
  2. you don’t need to change the status of the power irp when you complete it as you could be changing a failure to a success

Yes, thats an omission from my attempt to simplify the code for posting on the forum. The irp will be saved in the deviceExtension object which then gets passed around as context. and I will delete the IOStatus change.

the bigger problem is that by the type the set power irp completes, the underlying device could be powered off. A better approach would be to pend the set power irp, send the write, when the write completes, send the power irp down the stack (fire and forget, no need for a completion routine).

Let me fiddle around with that idea and see if it works. My experience has been that avoiding passing the power irp down the stack for any time at all results in a 9F bluescreen. This current solution I have implemented is the 3rd generation. Synchronous IO bluescreened and so did a worker thread.

Hi all. Thanks for your help. The completion routines are working as described, but I’m still detecting new IO after receiving PowerSystemShutdown.

I can’t find any documentation about when IO processing can be assumed to be finished… as far as I can tell the answer is “never”. The disk eventually gets powered off and any remaining IO will just fail, and it’s up the application to be resilient.

I’m trying to submit a final write to the disk before sending the power irp down the stack. Is it acceptable to fail any IRPs after I’ve started to my final write? Otherwise I’m stuck in an endless loop of getting new IO, writing from my driver… repeat.