KMDF defines two kinds of lock objects: wait locks (WDFWAITLOCK) and spin locks (WDFSPINLOCK). Wait locks synchronize operation at PASSIVE_LEVEL, and spin locks synchronize operation at DISPATCH_LEVEL or higher. For both lock types, the framework implements deadlock detection and tracks lock acquisition history.
Wait locks are PASSIVE_LEVEL synchronization mechanisms that are similar to the Windows event object. They cannot be acquired recursively; a driver must use the Windows mutex in situations that require a recursively acquired PASSIVE_LEVEL lock.
When a driver acquires a wait lock, it can supply a time-out period to limit the amount of time that the driver thread waits for the lock. If the driver does not supply a time-out, the acquisition method returns immediately if the lock is not available. If the driver supplies a time-out, it must acquire the lock at IRQL PASSIVE_LEVEL. If the driver supplies a zero-length time-out, it can acquire the lock at IRQL<=DISPATCH_LEVEL because the thread does not enter a wait state.
As a general rule, a driver should set the parent of a wait lock to the object that the lock protects. When the protected object is deleted, the framework also deletes the lock.
The Serial sample creates a context area for its interrupt object and reads and writes data in the context area from the EvtDeviceD0ExitPreInterruptsDisabled and EvtDeviceD0EntryPostInterruptsEnabled callbacks. The framework calls these callbacks at PASSIVE_LEVEL, so the driver creates a wait lock to synchronize access to the context area, as Listing 10-3 shows. The sample code in the listing appears in the Serial\Pnp.c file as part of the driver's EvtDriverDeviceAdd processing.
Listing 10-3: Creating a wait lock in a KMDF driver
![]() |
WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.ParentObject = pDevExt->WdfInterrupt; interruptContext = SerialGetInterruptContext(pDevExt->WdfInterrupt); status = WdfWaitLockCreate(&attributes, &interruptContext->InterruptStateLock );
![]() |
Before the driver creates the lock object, it initializes an object attributes structure and sets the ParentObject field to the interrupt object. The driver then retrieves a pointer to the interrupt object's context area so that it can store the handle to the wait lock in the context area. Finally, the driver calls WdfWaitLockCreate to create the lock, passing a pointer to the object attributes structure and a location in the context area in which to store the lock handle.
Listing 10-4 shows how the driver uses the lock to protect access to the context area. This code is from the EvtDeviceD0EntryPostInterruptsEnabled callback in the Serial\Pnp.c file.
Listing 10-4: Using a wait lock in a KMDF driver
![]() |
WdfWaitLockAcquire(interruptContext->InterruptStateLock, NULL); interruptContext->IsInterruptConnected = TRUE; WdfWaitLockRelease(interruptContext->InterruptStateLock);
![]() |
In the listing the driver acquires the lock without a time-out period, thus indicating that it will wait indefinitely. After the driver acquires the lock, the driver sets the value of IsInterruptConnected in the interrupt object context area. It then releases the lock.
Even though the context area is associated with the driver's interrupt object, the driver does not require the interrupt spin lock to protect the context area. In this sample, the driver reads and writes the context area only in callbacks that run at PASSIVE_LEVEL; it never accesses the context area at a higher IRQL. The framework serializes calls to the EvtDeviceD0EntryPostInterruptsEnabled callbacks for the device object, so the driver does not require a lock that can be recursively acquired. Therefore, the driver can use a KMDF wait lock.
A spin lock does exactly what its name implies: while one thread owns a spin lock, any other threads that are waiting to acquire the lock "spin" until the lock is available. In practice, a thread acquires the lock by replacing the contents of a particular memory location with a previously agreed-upon value. As long as the location contains the "acquired" value, the thread owns the lock. Other threads that are trying to acquire the lock recursively test the location. As soon as the value changes to "free," another thread can acquire the lock and replace the contents with the "acquired" value. The threads do not block-that is, they are not suspended or paged out. Instead, in a multiprocessor system, each thread retains control of the CPU, thus preventing execution of other code at the same or a lower IRQL.
Spin locks are the only synchronization mechanism that can be used at DISPATCH_ LEVEL or higher. Code that holds a spin lock runs at IRQL>=DISPATCH_LEVEL, which means that the system's thread-switching code-the dispatcher-cannot run and therefore the current thread cannot be preempted. Drivers should hold spin locks for only the minimum required amount of time and eliminate from the locked code path any tasks that do not require locking. Holding a spin lock for an unnecessarily long duration can hurt system-wide performance.
KMDF defines two types of spin locks:
Ordinary spin locks, which work at DISPATCH_LEVEL.
Chapter 16 describes interrupt spin locks An interrupt spin lock, which serializes two EvtInterruptIsr callbacks at DIRQL.
If your driver creates two or more interrupt objects and their respective EvtInterruptIsr callbacks access shared data, you should create an interrupt spin lock to serialize access to the data.
Chapter 15 describes the IRQL guidelines KMDF spin locks can have object context areas that the driver uses to store lock-specific data. All code within a spin lock must conform to the guidelines for running at IRQL>=DISPATCH_LEVEL.
The Pcidrv sample creates three spin locks. The spin locks protect certain fields of the device object's context area, a list of send buffers, and a list of receive buffers. Listing 10-5 shows how the Pcidrv sample creates the spin lock that protects the device object. The code in this example is from the Pcidrv\Sys\Hw\Nic_init.c file.
Listing 10-5: Creating a spin lock in a KMDF driver
![]() |
WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.ParentObject = FdoData->WdfDevice; status = WdfSpinLockCreate(&attributes,&FdoData->Lock); if(!NT_SUCCESS(status)){ return status; }
![]() |
To create a spin lock that has the device object as its parent, the driver initializes an object attributes structure and sets the ParentObject field to the device object handle. The driver then calls WdfSpinLockCreate, passing the attributes structure and a location to receive the spin lock handle. The driver stores the spin lock handle in the Lock field of the device object context area, to which FdoData points.
The Pcidrv sample driver does not configure a synchronization scope. Instead, it accepts the default of no synchronization and uses locks to protect data structures from concurrent access. Listing 10-6 shows how the driver acquires the locks that protect the device object and the receive buffer list before accessing them. This code appears in the Pcidrv\Sys\Hw\Nic_req.c file.
Listing 10-6: Acquiring and releasing a spin lock
![]() |
WdfSpinLockAcquire(FdoData->Lock); WdfSpinLockAcquire(FdoData->RcvLock); if (MP_TEST_FLAG(FdoData, fMP_ADAPTER_LINK_DETECTION)) { status = WdfRequestForwardToIoQueue(Request, FdoData->PendingIoctlQueue); WdfSpinLockRelease(FdoData->RcvLock); WdfSpinLockRelease(FdoData->Lock); . . . //Additional code omitted for brevity } WdfSpinLockRelease(FdoData->RcvLock); WdfSpinLockRelease(FdoData->Lock);
![]() |
In this listing, the lock that is stored at FdoData->Lock protects the device object, and the lock that is stored at FdoData->RcvLock protects the receive buffer list. The driver acquires both locks. To acquire a lock, the driver calls WdfSpinLockAcquire with the handle to the lock. To release a lock, the driver calls WdfSpinLockRelease, also with the handle to the lock. To prevent any possible deadlocks, the driver releases the locks in the opposite order in which it acquired them.
Tip | If your driver uses more than one lock, always acquire the locks in the same order and release them in the opposite order of acquisition. That is, if your driver acquires lock A before lock B, it should release lock B before releasing lock A. Otherwise, a deadlock can occur. You should also enable Deadlock Detection in Driver Verifier. When Deadlock Detection is enabled, Driver Verifier checks for violations of the locking hierarchy. |
If you noticed in the previous example that the Pcidrv driver stores the lists of send and receive buffers in the device object context area, you might wonder why it creates three separate locks when one lock for the entire device context area would suffice. The reason is performance. By individually protecting the lists, the driver can avoid acquiring the more general device object lock when it requires access only to a single list. For example, the driver acquires only the receive list lock when it handles a receive operation so that another driver callback that handles send operations can acquire the send lock at the same time.