When you call KeWaitForSingleObject or KeWaitForMultipleObjects you can provide a pointer to a timeout value. What value you pass for the pointer value makes a huge difference. This subtle distinction is something that first time driver writers do not grasp and get wrong.

If you want to infinitely wait, you pass NULL for the timeout. You must be at IRQL == PASSIVE_LEVEL to infinitely wait.

If you want to perform a test and exit (e.g. do not wait all, just try to acquire the handle and return immediately), you pass a pointer which points to 0x0. You can be at IRQL <= DISPATCH_LEVEL if you want to perform the tes and exit. This allows you to optimize a code path at DISPATCH_LEVEL and then queue to PASSIVE_LEVEL on in the case where you cannot acquire the handle.

Here is a summary of the different waits (excluding absolute):

Behavior Code
Infinitely wait
KeWaitForSingleObject(<dispatcher object>,
                      Executive,
                      KernelMode,
                      FALSE,
                      NULL);
Test and exit
LARGE_INTEGER timeout;

timeout.QuadPart = 0x0;

KeWaitForSingleObject(<dispatcher object>,
                      Executive,
                      KernelMode,
                      FALSE,
                      &timeout);
Relative timeout of 500 ms
LARGE_INTEGER timeout;

timeout.QuadPart = -5 * 10   // 100 ns to 1 us
                      * 1000 // us to ms
                      * 100; // 1ms to 100 ms

KeWaitForSingleObject(<dispatcher object>,
                      Executive,
                      KernelMode,
                      FALSE,
                      &timeout);