Background

One of the most important complaints in the packetbugs page - perhaps the most important - is that the NT DDK PACKET sample lacks any logic to cancel an IRP. When Rick Rigby read the packetbugs page he recognized this as a problem he'd already solved. He did the rest of us the favor of sharing his solution by mailing it to me.

Rick's commentary

The Cancel routine. There's no guaranty that this is perfect. It has not been tested on an SMP machine. It appears to work fine for our application. Note that we had to save away the OPEN_INSTANCE pointer in the Device Extension in PacketOpen(). There's probably a more elegant way to get this pointer, but I don't know it !

This code was built by reading NT Insider and some Microsoft samples & some good old-fashioned trial & error.

Rick's code

//
// The Cancel routine, Cancels any pending reads.
//
//  Without this routine the IO Manager waits 5 minutes for
//  each pending read before continuing ! This appears to
//  the user as though we've hung-up NT, requiring a reboot.
//  (See "NT Insider, December 1997, Cancel operations"
//
void ReadCancelRoutine( PDEVICE_OBJECT DeviceObject, PIRP Irp )
{
  PDEVICE_EXTENSION    DevExtension = DeviceObject->DeviceExtension;
  PLIST_ENTRY          ListEntry = NULL, NextElement = NULL;
  PNDIS_PACKET         pPacket;
  PPACKET_RESERVED     Reserved;
  PIRP                 CancelIrp = NULL;
  KIRQL                Irql;
  KIRQL                CancelIrql = Irp->CancelIrql;
  PIO_STACK_LOCATION   IrpSp;
  POPEN_INSTANCE       Open;

  //
  // Release Cancel IRQL now
  //
  IoReleaseCancelSpinLock( CancelIrql );

  // We squirreled away the "OpenInstance" pointer at
  // driver open time. Get it back now.
  Open = (POPEN_INSTANCE)DevExtension->ContextStructure;
  if( Open == NULL )
    {
    IF_LOUD(DbgPrint("OpenInstance Ptr NULL during Cx ??\n");)
    return;
    }

  //
  // Spinlock it.
  //
  KeAcquireSpinLock( &Open->CancelSpinLock, &Irql );

  //
  // Find the first entry in our QUEUE with the IRP set to CANCEL, 
  // complete the IRP with Status=CANCELLED
  //
  for( NextElement = Open->RcvList.Flink;
       NextElement != &Open->RcvList; )
    {
    Reserved = CONTAINING_RECORD( NextElement,PACKET_RESERVED,ListElement );
    pPacket  = CONTAINING_RECORD( Reserved,NDIS_PACKET,ProtocolReserved );
    ListEntry = NextElement;
    NextElement = NextElement->Flink;
    CancelIrp = RESERVED(pPacket)->Irp;
    if( CancelIrp->Cancel )
      {
      RemoveEntryList(ListEntry);
      IF_VERY_LOUD(DbgPrint("Cx IRP 0x%x\n",(ULONG)CancelIrp);)
      break;
      }
    CancelIrp = NULL; // Clear flag used below.
    }

  //
  // Release SpinLock
  //
  KeReleaseSpinLock( &Open->CancelSpinLock, Irql );

  //
  // Complete the Irp if one was found
  //
  if( CancelIrp )
    {
    PacketTransferDataComplete(Open,pPacket,STATUS_CANCELLED,0);
    }
  return;
}

My commentary

There's an important lack of generality in Rick's code of which he's well aware and which is rendered harmless by the way his application uses his driver. Because a single open instance pointer is saved in his device extension, this logic will break if more than one open instance is created.

A different way for the cancel routine to get an open instance pointer - whether it's the more elegant way Rick's commentary alludes to is for the reader to judge - can be found in the NT driver ntbgps.c in my free bootp client billgPC's zipfile.


<perin@acm.org>
Last modified: Sun May 10 12:11:42 1998