OSRLogo
OSRLogoOSRLogoOSRLogo x Seminar Ad
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Advantage: Driver Writer -- New Functions in the Windows XP DDK

If you’ve been writing NT drivers for a while, you probably have a set of favorite functions and techniques that you’ve come to rely on, and you use these over and over again. After all, who has time to read through the DDK documentation or scan through WDM.H every time a new DDK is released to see what new functions are available? Well, luckily you have your friends at OSR on your side of the court, and that’s just what we’ve done!

In this article, we describe several of the new Driver Development Interfaces (DDIs) that are available in WDM.H staring with the Windows XP DDK.. We think you’ll agree some of them are pretty useful.

ISR Spin Locks

Just about every developer who’s ever written a device driver will greet the introduction of KeAcquireInterruptSpinLock with a rousing “it’s about time!” This function complements the classic DDI KeSynchronizeExecution (which remains fully supported), and is used to serialize execution with a driver’s interrupt service routine. It takes a pointer to an Interrupt Object as input.  The function raises to the synchronize IRQL associated with the Interrupt Object and acquires the spin lock associated with the Interrupt Object. The pre-spin lock IRQL is returned by the function.  The spin lock is released and the IRQL is returned to the pre spin lock IRQL by the function KeReleaseInterruptSpinLock (shown in Figure 1).

NTKERNELAPI
KIRQL
KeAcquireInterruptSpinLock (
    IN PKINTERRUPT Interrupt
    );
 NTKERNELAPI
VOID
KeReleaseInterruptSpinLock (
    IN PKINTERRUPT Interrupt,
    IN KIRQL OldIrql
);

Figure 1 KeAcquireInterruptSpinLock

You shouldn’t need to acquire the interrupt spin lock too frequently in a driver. But in many cases, such as when you need to program a set of registers without being interrupted by your device’s ISR, proper synchronization is vital. While KeSynchronizeExecution has always been available to do this, its unusual semantics (you provide a pointer to a BOOLEAN function that is to be called at synchronize IRQL and with the ISR spin lock held) have long been an annoyance to driver writers.

While KeAcquireInterruptSpinLock isn’t described in the DDK documentation yet, it is scheduled for inclusion soon.

Just Try to Acquire That Lock

While we’re talking about spin locks, we should probably also note the introduction of KeTryToAcquireSpinLockAtDpc Level (shown in Figure 2). This function, which may only be called while running at IRQL DISPATCH_LEVEL, acquires the indicated Executive spin lock only if it is immediately available. If the lock is held, the function returns without having acquired the lock. Whether the lock was acquired or not by the call is indicated by the function’s return value: TRUE if the function returns with the lock acquired, FALSE otherwise.

NTKERNELAPI
BOOLEAN
FASTCALL
KeTryToAcquireSpinLockAtDpcLevel (
    IN PKSPIN_LOCK SpinLock
    );

Figure 2 KeTryToAcquireSpinLockAtDpcLevel

KeTryToAcquireSpinLockAtDpcLevel isn’t the sort of DDI that you need every day. But in the right circumstance, it can contribute to significantly higher overall system throughput. Because the function returns instead of busy waiting for the lock to become available, a driver can choose to either perform useful work before re-attempting the lock acquisition or to defer lock acquisition for a later time.

One place where this function’s unique features could be valuable is in the DpcForISR of a driver for a device that can have multiple requests outstanding simultaneously. As each request completes on the device, an interrupt is generated and a call to IoRequestDpc is made. On entering the DpcForISR, the driver might do the following:

  • Check the queue of pending write operations to see which, if any of these requests have completed;
  • Check the queue of pending read operations to see if any of these pending requests have completed;
  • Check the queue of pending device service requests, to see if any of these requests are pending or complete.

If each of the request queues described above were guarded by a separate spin lock (per device), a driver could avoid unnecessary busy waiting in the DpcForISR by using KeTryToAcquireSpinLockAtDpcLevel. If the lock on one queue is busy, this would mean that either a new request is being inserted in the queue or that an instance of the DpcForISR running on another processor was already processing the queue. By using KeTryToAcquireSpinLockAtDpcLevel the driver could proceed on to perform useful work, instead of (perhaps gratuitously) waiting for the lock on that queue to become available.

Send the IRP and Wait

Perhaps my favorite new DDI of all those introduced in Windows XP®, this function doesn’t do anything that was previously impossible or even difficult. Rather, it will help you avoid writing the same code over and over again. And anytime I can leave code out of my driver, it’s a win.

How many times have you written code in a dispatch routine shown in Figure 3?

IoCopyCurrentIrpStackLocationToNext(Irp);
   KeInitializeEvent(&event, NotificationEvent, FALSE);
   IoSetCompletionRoutine(Irp, SetEventCompleteRoutine, &event, TRUE, TRUE,TRUE);
   IoCallDriver(DeviceObject,IRP);
   KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

And the matching completion routine code:

 SetEventCompleteRoutine(PDEVICE_OBJECT Dev, PIRP Irp, PVOID Context) {
   PKEVENT eventToSet = (PKEVENT)Context;
  
   KeSetEvent(eventToSet, 0, FALSE);

   return(STATUS_MORE_PROCESSING_REQUIRED);

 Figure 3 IoCopyCurrentIrpStackLocationToNext

You’ve put it in every PnP driver you’ve ever written. And in who knows how many layered drivers or filter drivers. The point of this code (for the newbies among us) is to send an IRP down the stack, and properly reclaim it (stopping completion processing), once it’s been completed by all lower drivers.

Well, all of the above code, including the completion routine can now be replaced with the following:

       IoForwardIrpSynchronously(DeviceObject, Irp);

This function (shown in Figure 4) copies the current IRP stack location to the next, initializes an event, sends the IRP to the driver indicated by the DeviceObject parameter, and waits for the IRP to be completed. On return from IoForwardIrpSynchronously, your driver once again owns the IRP and all the drivers below yours have completed their processing.

NTKERNELAPI
BOOLEAN
IoForwardIrpSynchronously(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
);

Figure 4 IoForwaardlrpSynchronously

Like I said, this isn’t exactly a revolutionary function. But it’s a great convenience. It allows your code to “read” better, and it avoids your having to include the same code in every driver you write (thereby reducing the possibility of mistakes, and at least marginally reducing memory usage in the bargain).

Allocating Buffers

As driver writers, it seems like we’re always looking for “a better way” to allocate a block of memory to serve as a buffer. With the arrival of Windows XP, there’s a new function to consider: MmAllocatePagesForMdl (shown in
Figure 5).

NTKERNELAPI
PMDL
MmAllocatePagesForMdl (
    IN PHYSICAL_ADDRESS LowAddress,
    IN PHYSICAL_ADDRESS HighAddress,
    IN PHYSICAL_ADDRESS SkipBytes,
    IN SIZE_T TotalBytes
);

Figure 5 MmAllocatePagesForMdl

This function is a bit unusual in that it returns a pointer to an MDL that describes the allocated buffer. The MDL is allocated by the function from non-paged pool. The buffer returned will be non-paged, and zero-filled. It may be mapped by the caller as either cached or non-cached.

Designed specifically to facilitate support of AGP devices, the function takes a minimum and maximum physical address range from which the pages should be allocated. It also takes SkipBytes, which may be used to indicate how many bytes (which must be an integral number of pages) should be skipped between the base address of allocation attempts. If your driver doesn’t require the pages to come from any specific physical address range, just set:

LowAddress.QuadPart = 0,

HighAddress.QuadPart = (ULONGLONG)-1 and,

SkipBytes.QuadPart = 0

The only trick to using this function is that it is possible that it will return with a buffer that’s smaller than that requested. That is, the MDL returned may indicate a length shorter than TotalBytes in size. Therefore, when calling this function it is imperative that you check the returned MDL as shown in Figure 6).

If(MmGetMdlByteCount(Mdl) != TotalBytes) {

       //
       // Didn’t get the buffer!
       //
         
   }

Figure 6 Checking the Returned MDL

The allocated buffer can be mapped into kernel virtual address space by calling mMapLockedPagesSpecifyCache, and may be used for DMA by passing the returned MDL into ->GetScatterGatherList.

Pages allocated by MmAllocatePagesForMdl must be freed using MmFreePagesForMdl (shown in Figure 7).

NTKERNELAPI
VOID
MmFreePagesFromMdl (
    IN PMDL MemoryDescriptorList
);

Figure 7 Free the MDL!

Don’t forget to return the MDL itself using ExFreePool.

Cancel with System Queuing

Windows XP DDK also includes a few DDIs that make the always thorny issue of cancel processing at least a bit easier. One such function is IoSetStartIoAttributes, shown in Figure 8.

VOID
IoSetStartIoAttributes(
    IN PDEVICE_OBJECT DeviceObject,
    IN BOOLEAN DeferredStartIo,
    IN BOOLEAN NonCancelable
);

Figure 8 IoSetStartIoAttributes

This function simplifies cancel handling by allowing you to specify that:

a)      Your driver’s StartIo function should never be re-entered (for the same device), and/or,

b)       Once a request has been passed to your StartIo routine it (now being “in progress” on your device or are about to be) will never be cancelled.

While it might not be immediately obvious, these features simplify cancel handling for devices that use system queuing immensely. They entirely eliminate the need for such a driver acquiring the system-wide cancel spin lock (outside of its cancel routine).

Cancel-Safe Queuing

No discussion of either IRP cancellation or new DDI’s in the Windows XP DDK would be complete without a mention of the new Cancel Safe Queuing functions. These functions essentially let you to leave the problem of IRP cancellation to the I/O Manager, even if your driver queues its own IRPs.

You can use these cancel-safe queuing functions in your driver by allocating an IO_CSQ structure and initializing it with IoCsqInitialize (during driver initialization, typically in your AddDevice routine.
(See Figure 9).

 VOID IoCsqInitialize( PIO_CSQ Csq,
  IN PIO_CSQ_INSERT_IRP CsqInsertIrp,
  IN PIO_CSQ_REMOVE_IRP CsqRemoveIrp,
  IN PIO_CSQ_PEEK_NEXT_IRP CsqPeekNextIrp,
  IN PIO_CSQ_ACQUIRE_LOCK CsqAcquireLock,
  IN PIO_CSQ_RELEASE_LOCK CsqReleaseLock,
IN PIO_CSQ_COMPLETE_CANCELED_IRP CsqCompleteCanceledIrp );

Figure 9 Initialize the IO_CSQ Structure

This function takes as input pointers to functions that you implement that:

  • Lock and unlock your IRP queue;
  • Insert and remove IRPs on that queue;
  • Peek at an IRP in the queue; and,
  • Set the I/O status block to STATUS_CANCELLED and,
  • Call IoCompleteRequest to cancel a specific IRP.

In the main line code of your driver, you call IoCsqInsertIrp to insert an IRP on your driver’s queue, and IoCsqRemoveNextIrp to remove an IRP from the queue when you’re ready to process it. You can even remove a specific IRP by calling the function IoCsqRemoveIrp (see Figure 10).

IoCsqInsertIrp(PIO_CSQ Csq, PIRP Irp, PIO_CSQ_CONTEXT Context)

 IoCsqRemoveNextIrp(PIO_CSQ Csq, PVOID PeekContext)

 IoCsqRemoveIrp(PIRP Irp, PIO_CSQ_CONTEXT Context)


Figure 10 Inserting/Removing IRPs with IoCsq Functions

Using these functions, IRP cancellation is pretty much a non-issue. You don’t even implement a cancel routine in your driver! The only way cancel processing can be made easier is if you don’t implement it at all. The only non-obvious thing to be aware of when using these functions is that they make use of the IRP’s DriverContext[3] entry.

An added bonus of these functions is that they can be also used under Windows 2000®. The Win2K build environment of the XP DDK includes the header CSQ.H and the library CSQ.LIB that defines these functions. This allows you to build your Win2K driver with the static library, and make use of these functions.

There’s a very good example in the DDK’s \general directory that illustrates the use of these functions. It also shows how to choose whether or not to link with the CSQ.LIB library, depending on the build environment used.

 

Related Articles
WINVER Incorrectly Defined in XP/.NET Beta DDK's Win2K Build Environment
Getting Started Writing Windows Drivers
XP DDK Resets PATH Environment Variable
New DDK Package -- The DDK Suite (Update)
Need the XP DDK FAST?
WDM.H or NTDDK.H?
Must Use New DDK Compiler
Windows XP® DDK
Interview: All About the DDK
Guest Article: Simplifying Development with DDK Macros

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

Post Your Comments.
Print this article.
Email this article.
bottom nav links