1 /*++
2 
3 Copyright (c) Microsoft Corporation
4 
5 Module Name:
6 
7     FxInterruptThreadpoolUm.cpp
8 
9 Abstract:
10 
11     Threadpool functions for interrupt handling
12 
13 Author:
14 
15 
16 
17 
18 Environment:
19 
20     User mode only
21 
22 Revision History:
23 
24 --*/
25 
26 #include "fxmin.hpp"
27 #include "FxInterruptThreadpoolUm.hpp"
28 
29 extern "C" {
30 #include "FxInterruptThreadpoolUm.tmh"
31 }
32 
33 #define STRSAFE_LIB
34 #include <strsafe.h>
35 
36 FxInterruptThreadpool::FxInterruptThreadpool(
37     PFX_DRIVER_GLOBALS FxDriverGlobals
38     ) :
39     FxGlobalsStump(FxDriverGlobals),
40     m_Pool(NULL),
41     m_MinimumThreadCount(MINIMUM_THREAD_COUNT_DEFAULT)
42 {
43     InitializeThreadpoolEnvironment(&m_CallbackEnvironment);
44 }
45 
46 FxInterruptThreadpool::~FxInterruptThreadpool()
47 {
48     //
49     // close pool
50     //
51     if (m_Pool != NULL) {
52         CloseThreadpool(m_Pool);
53         m_Pool = NULL;
54     }
55 
56     DestroyThreadpoolEnvironment(&m_CallbackEnvironment);
57 }
58 
59 HRESULT
60 FxInterruptThreadpool::_CreateAndInit(
61     _In_ PFX_DRIVER_GLOBALS DriverGlobals,
62     _Out_ FxInterruptThreadpool** ppThreadpool
63     )
64 {
65     HRESULT hr;
66     FxInterruptThreadpool* pool = NULL;
67 
68     FX_VERIFY_WITH_NAME(INTERNAL, CHECK_NOT_NULL(ppThreadpool),
69         DriverGlobals->Public.DriverName);
70 
71     *ppThreadpool = NULL;
72 
73     pool = new (DriverGlobals) FxInterruptThreadpool(DriverGlobals);
74     if (pool == NULL)
75     {
76         hr = ERROR_NOT_ENOUGH_MEMORY;
77         DoTraceLevelMessage(DriverGlobals,
78                     TRACE_LEVEL_ERROR, TRACINGPNP,
79                     "FxInterruptThreadpool creation failed, "
80                     "%!hresult!", hr);
81         return hr;
82     }
83 
84     hr = pool->Initialize();
85 
86     if (SUCCEEDED(hr))
87     {
88         *ppThreadpool = pool;
89     }
90     else {
91         delete pool;
92     }
93 
94     return hr;
95 }
96 
97 HRESULT
98 FxInterruptThreadpool::Initialize(
99     )
100 {
101     HRESULT hr = S_OK;
102     DWORD error;
103     BOOL bRet;
104 
105     //
106     // Create a thread pool using win32 APIs
107     //
108     m_Pool = CreateThreadpool(NULL);
109     if (m_Pool == NULL)
110     {
111         error = GetLastError();
112         hr = HRESULT_FROM_WIN32(error);
113         DoTraceLevelMessage(GetDriverGlobals(),
114                     TRACE_LEVEL_ERROR, TRACINGPNP,
115                     "Threadpool creation failed, %!winerr!", error);
116         return hr;
117     }
118 
119     //
120     // Set maximum thread count to equal the total number of interrupts.
121     // Set minimum thread count (persistent threads) to equal the lower of
122     // number of interrupt and number of processors.
123     //
124     // We want minimum number of persistent threads to be at least equal to the
125     // number of interrupt objects so that there is no delay in servicing the
126     // interrupt if there are processors available (the delay can come due to
127     // thread pool delay in allocating and initializing a new thread). However,
128     // there is not much benefit in having more persistent threads than
129     // processors, as threads would have to wait for processor to become
130     // available anyways. Therefore, we chose to have the number of persistent
131     // equal to the Min(number of interrupts, number of processors).
132     //
133     // In the current design, if there are more interrupts than
134     // processors, as soon as one thread finishes servicing the interrupt, both
135     // the thread and processor will be available to service the next queued
136     // interrupt. Note that thread pool will queue all the waits and will
137     // satisfy them in a FIFO manner.
138     //
139     // Since we don't know the number of interrupts in the beginning, we will
140     // start with one and update it when we know the actual number of interrupts
141     // after processing driver's OnPrepareHardware callback.
142     //
143     // Note on interrupt servicing:
144     // When fx connects the interrupt, it queues an event-based wait block
145     // to thread pool for each interrupt, so that the interrupt can get serviced
146     // using one of the thread pool threads when the auto-reset event shared
147     // with reflector is set by reflector in the DpcForIsr routine queued by Isr.
148     // While the interrupt is being serviced by one of the threads, no wait block
149     // is queued to thread pool for that interrupt, so arrival of same interrupt
150     // signals the event in DpcForIsr but doesn't cause new threads to pick up
151     // the servicing. Note that other interrupts still have their wait blocks
152     // queued so they will be serviced as they arrive.
153     //
154     // When previous servicing is over (i.e. the ISR callback has been
155     // invoked and it has returned), Fx queues another wait block to thread pool
156     // for that interrupt. If the event is already signalled, it would result in
157     // the same thread (or another) picking up servicing immediately.
158     //
159     // This means the interrupt ISR routine can never run concurrently
160     // for same interrupt. Therefore, there is no need to have more than one
161     // thread for each interrupt.
162     //
163     SetThreadpoolThreadMaximum(m_Pool, 1);
164 
165     //
166     // Create one persistent thread since atleast one interrupt is the most
167     // likely scenario. We will update this number to have either the same
168     // number of threads as there are interrupts, or the number of
169     // processors on the machine.
170     //
171     bRet = SetThreadpoolThreadMinimum(m_Pool, m_MinimumThreadCount);
172     if (bRet == FALSE) {
173         error = GetLastError();
174         hr = HRESULT_FROM_WIN32(error);
175         DoTraceLevelMessage(GetDriverGlobals(),
176                     TRACE_LEVEL_ERROR, TRACINGPNP,
177                     "%!FUNC!: Failed to set minimum threads (%d) in threadpool,"
178                     " %!winerr!", m_MinimumThreadCount, error);
179         goto cleanup;
180     }
181 
182     //
183     // Associate thread pool with callback environment.
184     //
185     SetThreadpoolCallbackPool(&m_CallbackEnvironment, m_Pool);
186 
187 cleanup:
188 
189     if (FAILED(hr)) {
190         CloseThreadpool(m_Pool);
191         m_Pool = NULL;
192     }
193 
194     return hr;
195 }
196 
197 HRESULT
198 FxInterruptThreadpool::UpdateThreadPoolThreadLimits(
199     _In_ ULONG InterruptCount
200     )
201 {
202     BOOL bRet;
203     HRESULT hr = S_OK;
204     SYSTEM_INFO sysInfo;
205     ULONG minThreadCount = 0;
206     ULONG procs;
207     DWORD error;
208 
209     FX_VERIFY(INTERNAL, CHECK_NOT_NULL(m_Pool));
210 
211     //
212     // if there are more than one interrupts then we need to update minimum
213     // thread count.
214     //
215     if (m_MinimumThreadCount >= InterruptCount) {
216         //
217         // nothing to do
218         //
219         return S_OK;
220     }
221 
222     //
223     // We want to have number of minimum persistent threads
224     // = Min(number of interrupts, number of processors).
225     // See comments in Initialize routine for details.
226     //
227     GetSystemInfo(&sysInfo);
228     procs = sysInfo.dwNumberOfProcessors;
229 
230     minThreadCount = min(InterruptCount, procs);
231 
232     if (m_MinimumThreadCount < minThreadCount) {
233         //
234         // Set threadpool min
235         //
236         bRet = SetThreadpoolThreadMinimum(m_Pool, minThreadCount);
237         if (bRet == FALSE) {
238             error = GetLastError();
239             hr = HRESULT_FROM_WIN32(error);
240             DoTraceLevelMessage(GetDriverGlobals(),
241                         TRACE_LEVEL_ERROR, TRACINGPNP,
242                         "Failed to set minimum threads in threadpool,"
243                         " TP_POOL 0x%p to %d %!winerr!", m_Pool, minThreadCount,
244                         error);
245             return hr;
246         }
247 
248         m_MinimumThreadCount = minThreadCount;
249     }
250 
251     //
252     // set thread pool max to max number of interrupts
253     //
254     SetThreadpoolThreadMaximum(m_Pool, InterruptCount);
255 
256     DoTraceLevelMessage(GetDriverGlobals(),
257                 TRACE_LEVEL_ERROR, TRACINGPNP,
258                 "Threads in thread pool TP_POOL 0x%p updated"
259                 " to Max %d Min %d threads", m_Pool,
260                 InterruptCount, minThreadCount);
261 
262     return hr;
263 }
264 
265 FxInterruptWaitblock::~FxInterruptWaitblock(
266     VOID
267     )
268 {
269     //
270     // close the thread pool wait structure
271     //
272     if (m_Wait) {
273         //
274         // Make sure no event is registered.
275         //
276         ClearThreadpoolWait();
277 
278         //
279         // Wait for all the callbacks to finish.
280         //
281         WaitForOutstandingCallbackToComplete();
282 
283         //
284         // close the wait
285         //
286         CloseThreadpoolWait();
287 
288         m_Wait = NULL;
289     }
290 
291     //
292     // close event handle
293     //
294     if (m_Event) {
295         CloseHandle(m_Event);
296         m_Event = NULL;
297     }
298 }
299 
300 HRESULT
301 FxInterruptWaitblock::_CreateAndInit(
302     _In_ FxInterruptThreadpool* Threadpool,
303     _In_ FxInterrupt* Interrupt,
304     _In_ PTP_WAIT_CALLBACK WaitCallback,
305     _Out_ FxInterruptWaitblock** Waitblock
306     )
307 {
308     HRESULT hr = S_OK;
309     FxInterruptWaitblock* waitblock = NULL;
310     PFX_DRIVER_GLOBALS driverGlobals;
311 
312     FX_VERIFY(INTERNAL, CHECK_NOT_NULL(Waitblock));
313     *Waitblock = NULL;
314     driverGlobals = Interrupt->GetDriverGlobals();
315 
316     //
317     // create an instance of interrupt wait block
318     //
319     waitblock = new (driverGlobals) FxInterruptWaitblock(driverGlobals);
320     if (waitblock == NULL) {
321         hr = E_OUTOFMEMORY;
322         DoTraceLevelMessage(driverGlobals,
323                     TRACE_LEVEL_ERROR, TRACINGPNP,
324                     "Waitblock creation failed %!hresult!", hr);
325         goto exit;
326     }
327 
328     hr = waitblock->Initialize(Threadpool,
329                                Interrupt,
330                                WaitCallback);
331     if (SUCCEEDED(hr)) {
332         *Waitblock = waitblock;
333     }
334     else {
335         DoTraceLevelMessage(driverGlobals,
336                     TRACE_LEVEL_ERROR, TRACINGPNP,
337                     "Waitblock init failed %!hresult!", hr);
338     }
339 
340 exit:
341 
342     if(FAILED(hr) && waitblock != NULL) {
343         delete waitblock;
344     }
345 
346     return hr;
347 }
348 
349 HRESULT
350 FxInterruptWaitblock::Initialize(
351     __in FxInterruptThreadpool* Threadpool,
352     __in FxInterrupt* Interrupt,
353     __in PTP_WAIT_CALLBACK WaitCallback
354     )
355 {
356     HRESULT hr = S_OK;
357     DWORD error;
358 
359     //
360     // create a per-interrupt auto-reset event, non-signalled to begin with.
361     //
362     m_Event = CreateEvent(
363                           NULL,  // LPSECURITY_ATTRIBUTES lpEventAttributes,
364                           FALSE, // BOOL bManualReset,
365                           FALSE, // BOOL bInitialState,
366                           NULL   // LPCTSTR lpName
367                           );
368 
369     if (m_Event == NULL) {
370         error = GetLastError();
371         hr = HRESULT_FROM_WIN32(error);
372         DoTraceLevelMessage(GetDriverGlobals(),
373                     TRACE_LEVEL_ERROR, TRACINGPNP,
374                     "Event creation failed for FxInterrupt object"
375                     " %!winerr!", error);
376         goto exit;
377     }
378 
379     //
380     // create a per-interrupt thread pool wait structure. This wait structure is
381     // needed to associate an event-based wait callback with threadpool.
382     //
383     m_Wait = Threadpool->CreateThreadpoolWait(WaitCallback,
384                                               Interrupt);
385     if (m_Wait == NULL) {
386         error = GetLastError();
387         hr = HRESULT_FROM_WIN32(error);
388         DoTraceLevelMessage(GetDriverGlobals(),
389                     TRACE_LEVEL_ERROR, TRACINGPNP,
390                     "Event creation failed for FxInterrupt object"
391                     " %!winerr!", error);
392         goto exit;
393     }
394 
395 exit:
396 
397     return hr;
398 }
399 
400 
401