This example shows the addition of timeout support to the simple parallel port driver developed in previous chapters. Device Extension AdditionsThe Device Extension is modified to include the timeout counter. A counter value of -1 signifies that timeouts are to be ignored. typedef struct _DEVICE_EXTENSION { ... LONG timeRemaining; // timeout counter - seconds ... } DEVICE_EXTENSION, *PDEVICE_EXTENSION; AddDevice AdditionsAddDevice is changed to initialize the I/O Timer for the device. The counter value does not need to be initialized until the timer is actually started. NTSTATUS AddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pdo) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; ... // Near the end of the function, after IoCreateDevice // has been called.... // // Create the I/O Timer IoInitializeTimer( pdo, IoTimer, pDevExt); ... } Create Dispatch Routine ChangesWhen the device is "opened" from user-mode (via the Win32 CreateFile call), the I/O Timer is started. It continues to tick so long as the handle remains open. Since the time is ticking, the timeout counter must be initialized to show that at present the ticks should be ignored. NTSTATUS DispatchCreate( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; ... // Near the end of the function, the timeout // counter is initialized and the I/O Timer // is started. pDevExt->timeRemaining = -1; IoStartTimer( pDevObj ); ... } StartIo ChangesEach time the physical device is started and an interrupt becomes expected, the maximum number of seconds to wait for the interrupt must be set into the timeout counter. An operational counter must be synchronized with all code paths to ensure it does not become corrupted. The ISR and I/O Timer callback routines, running as interrupting code paths, also read and write the counter. The use of InterlockedExchange assures that the 32-bit timeout counter is stored atomically. VOID StartIo( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; ... // Before physically starting the device, the // timeout counter must be set. Remember to // account for all forms of device latency, // including device power/spin-up, etc. InterlockedExchange( pDevExt->timeRemaining, INTERRUPT_TIMEOUT ); // // Now start the device: CallTransmitBytes( pDevObj, pIrp ); ... } ISR ChangesThe Interupt Service Routine is modified so that each time an expected interrupt arrives, the I/O Timeout wait period is either canceled or reset. If the ISR starts another device operation, a fresh timeout period is established. If there are no more pending transfer operations, the timeout is canceled (set to -1). BOOLEAN Isr( IN PKINTERRUPT pInterruptObj, IN PVOID pServiceContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pServiceContext; ... // If there are more bytes to send, // reset the timeout counter to a fresh start if (TransmitBytes( pDevExt ) InterlockedExchange(&pDevExt->timeRemaining, INTERRUPT_TIMEOUT ); // If no more bytes to send, clear the timeout else InterlockedExchange(&pDevExt->timeRemaining, -1 ); ... I/O Timer Callback RoutineFinally, the Timer Callback routine itself is presented. If the routine detects that the timeout has expired, it uses the driver's DprForIsr routine to fail the IRP. VOID IoTimer( IN PDEVICE_OBJECT pDevObj, IN PVOID pContext ) { PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pContext; // Check the timeout value if (InterlockedCompareExchange( &pDevExt->timeRemaining, -1, -1) < 0) return; // timer not active // Since the timer is active, decrement it if (InterlockedDecrement(&devExt->timeRemaining) == 0) { // timeout has expired - fail the IRP InterlockedExchange(&pDevExt->timeRemaining, -1 ); DpcForIsr( NULL, pDevObj, pDevObj->CurrentIrp, pDevExt ); } return; } There is a small window of interest between the check to see if the timer is active and the decrementing of the timer. Between the two calls, the ISR could execute, setting the (formerly) active timer counter to -1. When theIoTimer routine regains control, it decrements the -1 to -2. The code accounts for this window by comparing the timeRemaining value to any negative value.
|