1 /*++
2 
3 Copyright (c) Microsoft Corporation
4 
5 Module Name:
6 
7     InterruptObject.cpp
8 
9 Abstract:
10 
11     This module implements a frameworks managed interrupt object
12 
13 Author:
14 
15 
16 
17 
18 Environment:
19 
20     Kernel mode only
21 
22 Revision History:
23 
24 
25 
26 
27 
28 
29 --*/
30 
31 #include "pnppriv.hpp"
32 
33 // Tracing support
34 // #include "InterruptObjectKm.tmh"
35 
36 _Must_inspect_result_
37 NTSTATUS
38 FxInterrupt::InitializeInternal(
39     __in FxObject*  Parent,
40     __in PWDF_INTERRUPT_CONFIG Configuration
41     )
42 {
43     UNREFERENCED_PARAMETER(Parent);
44     UNREFERENCED_PARAMETER(Configuration);
45 
46     DO_NOTHING();
47 
48     return STATUS_SUCCESS;
49 }
50 
51 VOID
52 FxInterrupt::DpcHandler(
53     __in_opt PVOID SystemArgument1,
54     __in_opt PVOID SystemArgument2
55     )
56 {
57     UNREFERENCED_PARAMETER(SystemArgument1);
58     UNREFERENCED_PARAMETER(SystemArgument2);
59 
60     ASSERT(m_EvtInterruptDpc != NULL);
61 
62     FX_TRACK_DRIVER(GetDriverGlobals());
63 
64     //
65     // Call the drivers registered DpcForIsr event callback
66     //
67     if (m_CallbackLock != NULL) {
68         KIRQL irql = 0;
69 
70         m_CallbackLock->Lock(&irql);
71         m_EvtInterruptDpc(GetHandle(), m_Device->GetHandle());
72         m_CallbackLock->Unlock(irql);
73     }
74     else {
75         m_EvtInterruptDpc(GetHandle(), m_Device->GetHandle());
76     }
77 
78     return;
79 }
80 
81 BOOLEAN
82 FxInterrupt::QueueDpcForIsr(
83     VOID
84     )
85 {
86     BOOLEAN queued;
87 
88     //
89     // Using this function is optional,
90     // but the caller better have registered a handler
91     //
92     ASSERT(m_EvtInterruptDpc != NULL);
93 
94     queued = KeInsertQueueDpc(&m_Dpc, this, NULL);
95 
96     return queued;
97 }
98 
99 VOID
100 FxInterrupt::WorkItemHandler(
101     VOID
102     )
103 {
104     ASSERT(m_EvtInterruptWorkItem != NULL );
105 
106     FX_TRACK_DRIVER(GetDriverGlobals());
107 
108     //
109     // Call the drivers registered WorkItemForIsr event callback
110     //
111     if (m_CallbackLock != NULL) {
112         KIRQL irql = 0;
113 
114         m_CallbackLock->Lock(&irql);
115 
116         FxPerfTraceWorkItem(&m_EvtInterruptWorkItem);
117         m_EvtInterruptWorkItem(GetHandle(), m_Device->GetHandle());
118         m_CallbackLock->Unlock(irql);
119     }
120     else {
121         FxPerfTraceWorkItem(&m_EvtInterruptWorkItem);
122         m_EvtInterruptWorkItem(GetHandle(), m_Device->GetHandle());
123     }
124 
125     return;
126 }
127 
128 _Must_inspect_result_
129 NTSTATUS
130 FxInterrupt::ConnectInternal(
131     VOID
132     )
133 {
134     IO_CONNECT_INTERRUPT_PARAMETERS connectParams;
135     FxPkgPnp* fxPkgPnp;
136 
137     fxPkgPnp = m_Device->m_PkgPnp;
138 
139     //
140     // Tell the PnP Manager to connect the interrupt.
141     //
142     ASSERT(fxPkgPnp->m_IoConnectInterruptEx != NULL);
143 
144     //
145     // We're running on Longhorn or later (or somebody backported the new
146     // interrupt code,) so tell the PnP manager everything we can about this
147     // device.
148     //
149     RtlZeroMemory(&connectParams, sizeof(connectParams));
150 
151     if (FxIsProcessorGroupSupported()) {
152         connectParams.Version = CONNECT_FULLY_SPECIFIED_GROUP;
153     }
154     else {
155         connectParams.Version = CONNECT_FULLY_SPECIFIED;
156     }
157 
158     connectParams.FullySpecified.PhysicalDeviceObject = m_Device->GetPhysicalDevice();
159     connectParams.FullySpecified.InterruptObject      = &m_Interrupt;
160     connectParams.FullySpecified.ServiceRoutine       = _InterruptThunk;
161     connectParams.FullySpecified.ServiceContext       = this;
162     connectParams.FullySpecified.SpinLock             = m_SpinLock;
163     connectParams.FullySpecified.FloatingSave         = m_FloatingSave;
164     connectParams.FullySpecified.Vector               = m_InterruptInfo.Vector;
165     connectParams.FullySpecified.Irql                 = m_InterruptInfo.Irql;
166     connectParams.FullySpecified.ProcessorEnableMask  = m_InterruptInfo.TargetProcessorSet;
167     connectParams.FullySpecified.Group                = m_InterruptInfo.Group;
168     connectParams.FullySpecified.InterruptMode        = m_InterruptInfo.Mode;
169     connectParams.FullySpecified.ShareVector          =
170         m_InterruptInfo.ShareDisposition == CmResourceShareShared ? TRUE : FALSE;
171     connectParams.FullySpecified.SynchronizeIrql      = m_SynchronizeIrql;
172 
173     return fxPkgPnp->m_IoConnectInterruptEx(&connectParams);
174 }
175 
176 VOID
177 FxInterrupt::DisconnectInternal(
178     VOID
179     )
180 {
181     IO_DISCONNECT_INTERRUPT_PARAMETERS params;
182     PKINTERRUPT interruptObject;
183     FxPkgPnp* fxPkgPnp;
184 
185     fxPkgPnp = m_Device->m_PkgPnp;
186 
187     //
188     // Now null these pointers so that we can catch anyone trying to use them
189     // erroneously.
190     //
191     interruptObject = m_Interrupt;
192     m_Interrupt = NULL;
193 
194     //
195     // Disconnect the interrupt.
196     //
197     ASSERT(fxPkgPnp->m_IoDisconnectInterruptEx != NULL);
198 
199     RtlZeroMemory(&params, sizeof(params));
200 
201     if (FxIsProcessorGroupSupported()) {
202         params.Version = CONNECT_FULLY_SPECIFIED_GROUP;
203     }
204     else {
205         params.Version = CONNECT_FULLY_SPECIFIED;
206     }
207 
208     params.ConnectionContext.InterruptObject = interruptObject;
209 
210     fxPkgPnp->m_IoDisconnectInterruptEx(&params);
211 
212     return;
213 }
214 
215 VOID
216 FxInterrupt::FilterResourceRequirements(
217     __inout PIO_RESOURCE_DESCRIPTOR IoResourceDescriptor
218     )
219 /*++
220 
221 Routine Description:
222 
223     This function allows an interrupt object to change the
224     IoResourceRequirementsList that the PnP Manager sends during
225     IRP_MN_FILTER_RESOURCE_REQUIREMENTS.  This function takes a single
226     IoResourceDescriptor and applies default or user specified policy.
227 
228 Arguments:
229 
230     IoResourceDescriptor - Pointer to descriptor that matches this interrupt object
231 
232 Return Value:
233 
234     VOID
235 
236 --*/
237 {
238     //
239     // Set sharing policy.
240     //
241     switch (m_ShareVector) {
242     case WdfTrue:
243         //
244         // Override the bus driver's value, explicitly sharing this interrupt.
245         //
246         IoResourceDescriptor->ShareDisposition = CmResourceShareShared;
247         break;
248 
249     case WdfFalse:
250         //
251         // Override the bus driver's value, explicitly claiming this interrupt
252         // as non-shared.
253         //
254         IoResourceDescriptor->ShareDisposition = CmResourceShareDeviceExclusive;
255         break;
256 
257     case WdfUseDefault:
258     default:
259         //
260         // Leave the bus driver's value alone.
261         //
262         break;
263     }
264 
265     //
266     // Apply policy.  Only do this if we are running on an OS which supports
267     // the notion of Interrupt Policy and if the policy is not already included
268     // by the bus driver based on registry settings.
269     //
270     if (FxLibraryGlobals.IoConnectInterruptEx != NULL &&
271         m_SetPolicy &&
272         (IoResourceDescriptor->Flags & CM_RESOURCE_INTERRUPT_POLICY_INCLUDED) == 0x0) {
273 
274         IoResourceDescriptor->Flags |= CM_RESOURCE_INTERRUPT_POLICY_INCLUDED;
275         IoResourceDescriptor->u.Interrupt.AffinityPolicy      = (IRQ_DEVICE_POLICY)m_Policy;
276         IoResourceDescriptor->u.Interrupt.PriorityPolicy      = (IRQ_PRIORITY)m_Priority;
277         IoResourceDescriptor->u.Interrupt.TargetedProcessors  = m_Processors.Mask;
278         IoResourceDescriptor->u.Interrupt.Group               = m_Processors.Group;
279     }
280 }
281 
282 VOID
283 FxInterrupt::FlushQueuedDpcs(
284     VOID
285     )
286 {
287     KeFlushQueuedDpcs();
288 }
289 
290 BOOLEAN
291 FxInterrupt::_InterruptThunk(
292     __in struct _KINTERRUPT *Interrupt,
293     __in PVOID              ServiceContext
294     )
295 
296 /*++
297 
298 Routine Description:
299 
300     This is the C routine called by the kernels INTERRUPT handler
301 
302 Arguments:
303 
304 Return Value:
305 
306 --*/
307 
308 {
309     FxInterrupt*    interrupt;
310     BOOLEAN         result;
311 
312     UNREFERENCED_PARAMETER(Interrupt);
313 
314     interrupt = (FxInterrupt*)ServiceContext;
315     ASSERT( interrupt->m_EvtInterruptIsr != NULL );
316 
317     if (interrupt->m_IsEdgeTriggeredNonMsiInterrupt == TRUE) {
318         //
319         // If KMDF is in the process of disconnecting this interrupt, discard it.
320         //
321         if (interrupt->m_Disconnecting == TRUE) {
322             return FALSE;
323         }
324 
325         //
326         // Captures the interrupt object as a backup in case interrupts start
327         // to arrive before IoConnectInterruptEx sets FxInterrupt.m_Interrupt.
328         //
329         interrupt->m_InterruptCaptured = Interrupt;
330     }
331     //
332     // If the interrupt is not connected, treat this as spurious interrupt.
333     //
334     else if (NULL == interrupt->m_Interrupt) {
335         return FALSE;
336     }
337 
338     if (interrupt->IsWakeCapable()) {
339         //
340         // if it is a wake capable interrupt, we will hand it off
341         // to the state machine so that it can power up the device
342         // if required and then invoke the ISR callback
343         //
344         ASSERT(interrupt->m_PassiveHandling);
345         FxPerfTracePassiveInterrupt(&interrupt->m_EvtInterruptIsr);
346         return interrupt->WakeInterruptIsr();
347     }
348 
349     if (interrupt->m_PassiveHandling) {
350         ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
351 
352         //
353         // Acquire our internal passive-lock after the kernel acquired its own
354         // passive-lock and before invoking the callback.
355         //
356         interrupt->AcquireLock();
357 
358         FxPerfTracePassiveInterrupt(&interrupt->m_EvtInterruptIsr);
359 
360         result = interrupt->m_EvtInterruptIsr(
361                                 interrupt->GetHandle(),
362                                 interrupt->m_InterruptInfo.MessageNumber);
363         interrupt->ReleaseLock();
364     }
365     else {
366 
367         FxPerfTraceInterrupt(&interrupt->m_EvtInterruptIsr);
368 
369         result = interrupt->m_EvtInterruptIsr(
370                                 interrupt->GetHandle(),
371                                 interrupt->m_InterruptInfo.MessageNumber);
372     }
373 
374     return result;
375 }
376 
377 VOID
378 FxInterrupt::_InterruptDpcThunk(
379     __in PKDPC Dpc,
380     __in_opt PVOID DeferredContext,
381     __in_opt PVOID SystemArgument1,
382     __in_opt PVOID SystemArgument2
383     )
384 /*++
385 
386 Routine Description:
387 
388    Thunk used to invoke EvtInterruptDpc at DPC-level, or to queue a work-item
389    for invoking EvtInterruptWorkItem at passive-level.
390 
391 --*/
392 {
393     FxInterrupt* interrupt;
394 
395     UNREFERENCED_PARAMETER(Dpc);
396 
397     ASSERT(DeferredContext != NULL);
398     interrupt = (FxInterrupt*) DeferredContext;
399 
400     if (interrupt->m_SystemWorkItem == NULL) {
401 
402         FxPerfTraceDpc(&interrupt->m_EvtInterruptDpc);
403 
404         interrupt->DpcHandler(SystemArgument1, SystemArgument2);
405     }
406     else {
407         interrupt->m_SystemWorkItem->TryToEnqueue(_InterruptWorkItemCallback,
408                                                   interrupt);
409     }
410 }
411 
412 VOID
413 FxInterrupt::FlushAndRundownInternal(
414     VOID
415     )
416 {
417     //
418     // Rundown the workitem.
419     //
420     if (m_SystemWorkItem != NULL) {
421         m_SystemWorkItem->DeleteObject();
422         m_SystemWorkItem = NULL;
423     }
424 
425     //
426     // If present, delete the default passive-lock.
427     //
428     if (m_DisposeWaitLock) {
429         ASSERT(m_WaitLock != NULL);
430         m_WaitLock->DeleteObject();
431         m_WaitLock = NULL;
432         m_DisposeWaitLock = FALSE;
433     }
434 }
435 
436 BOOLEAN
437 FxInterrupt::_InterruptMarkDisconnecting(
438     _In_opt_ PVOID SyncContext
439     )
440 {
441     FxInterrupt* pFxInterrupt;
442 
443     ASSERT(SyncContext != NULL);
444     pFxInterrupt = (FxInterrupt*)SyncContext;
445 
446     //
447     // This callback is invoked only if m_IsEdgeTriggeredNonMsiInterrupt
448     // is TRUE. This will cause _InterruptThunk to discard subsequent
449     // interrupts until m_Disconnecting is reset to FALSE.
450     //
451     pFxInterrupt->m_Disconnecting = TRUE;
452 
453     return TRUE;
454 }
455 
456 VOID
457 FxInterrupt::ReportActive(
458     _In_ BOOLEAN Internal
459     )
460 {
461     IO_REPORT_INTERRUPT_ACTIVE_STATE_PARAMETERS parameters;
462     FxPkgPnp* fxPkgPnp;
463 
464     fxPkgPnp = m_Device->m_PkgPnp;
465 
466     if (Internal == FALSE) {
467         //
468         // if the interrupt is not connected, you can't report active or inactive
469         //
470         if(m_Connected == FALSE || m_Interrupt == NULL) {
471             DoTraceLevelMessage(
472                 GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
473                 "Driver is reporting WDFINTERRUPT %p as being active even though"
474                 " it is not connected.",  GetHandle());
475             FxVerifierDbgBreakPoint(GetDriverGlobals());
476             return;
477         }
478 
479         if (fxPkgPnp->m_IoReportInterruptActive == NULL) {
480             DoTraceLevelMessage(
481                 GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
482                 "Driver is calling DDI WdfInterruptReportActive() on an OS that "
483                 "doesn't support the DDI.");
484             FxVerifierDbgBreakPoint(GetDriverGlobals());
485             return;
486         }
487     }
488 
489     //
490     // No need to report active if interrupt is already active
491     //
492     if (m_Active) {
493         return;
494     }
495 
496     RtlZeroMemory(&parameters, sizeof(parameters));
497 
498     if (FxIsProcessorGroupSupported()) {
499         parameters.Version = CONNECT_FULLY_SPECIFIED_GROUP;
500     }
501     else {
502         parameters.Version = CONNECT_FULLY_SPECIFIED;
503     }
504 
505     parameters.ConnectionContext.InterruptObject = m_Interrupt;
506 
507     fxPkgPnp->m_IoReportInterruptActive(&parameters);
508     m_Active = TRUE;
509 
510     return;
511 }
512 
513 VOID
514 FxInterrupt::ReportInactive(
515     _In_ BOOLEAN Internal
516     )
517 {
518     IO_REPORT_INTERRUPT_ACTIVE_STATE_PARAMETERS parameters;
519     FxPkgPnp* fxPkgPnp;
520 
521     fxPkgPnp = m_Device->m_PkgPnp;
522 
523     if (Internal == FALSE) {
524         //
525         // if the interrupt is not connected, you can't report active or inactive
526         //
527         if(m_Connected == FALSE || m_Interrupt == NULL) {
528             DoTraceLevelMessage(
529                 GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
530                 "Driver is reporting WDFINTERRUPT %p as being inactive even though"
531                 " it is not connected.",  GetHandle());
532             FxVerifierDbgBreakPoint(GetDriverGlobals());
533             return;
534         }
535 
536         if (fxPkgPnp->m_IoReportInterruptInactive == NULL) {
537             DoTraceLevelMessage(
538                 GetDriverGlobals(), TRACE_LEVEL_WARNING, TRACINGPNP,
539                 "Driver is calling DDI WdfInterruptReportInactive() on an OS that "
540                 "doesn't support the DDI.");
541             FxVerifierDbgBreakPoint(GetDriverGlobals());
542             return;
543         }
544     }
545 
546     //
547     // No need to report Inactive if interrupt is already Inactive
548     //
549     if (m_Active == FALSE) {
550         return;
551     }
552 
553     RtlZeroMemory(&parameters, sizeof(parameters));
554 
555     if (FxIsProcessorGroupSupported()) {
556         parameters.Version = CONNECT_FULLY_SPECIFIED_GROUP;
557     }
558     else {
559         parameters.Version = CONNECT_FULLY_SPECIFIED;
560     }
561 
562     parameters.ConnectionContext.InterruptObject = m_Interrupt;
563 
564     fxPkgPnp->m_IoReportInterruptInactive(&parameters);
565     m_Active = FALSE;
566 
567     return;
568 }
569 
570