1 /*++ 2 3 Copyright (c) Microsoft Corporation 4 5 ModuleName: 6 7 MxWorkItemUm.h 8 9 Abstract: 10 11 User mode implementation of work item 12 class defined in MxWorkItem.h 13 14 ***********PLEASE NOTE***************************** 15 A significant difference from kernel mode implementation of work item 16 is that user mode version of MxWorkItem::_Free synchronously waits for 17 callback to return. 18 19 This implies that _Free cannot be invoked from within the callback otherwise 20 it would lead to a deadlock. 21 22 PLEASE NOTE that _Free cannot be made to return without waiting without 23 significant changes. 24 25 If Free is not made to wait synchronously there is a potential for binary 26 unload while workitem is running - even with waiting for an event/reference 27 count etc., the tail instructions may be running. 28 The only way to resolve that is to move work-item code out of framework 29 binary and into the host so that host can take a reference on framework 30 binary around the work item callback invocation (similar to the way I/O 31 manager keeps a reference on the device object around the invocation of 32 33 workitem callback). 34 **************************************************** 35 36 Author: 37 38 39 40 Revision History: 41 42 43 44 45 46 47 --*/ 48 49 #pragma once 50 51 typedef 52 VOID 53 MX_WORKITEM_ROUTINE ( 54 __in MdDeviceObject DeviceObject, 55 __in_opt PVOID Context 56 ); 57 58 typedef MX_WORKITEM_ROUTINE *PMX_WORKITEM_ROUTINE; 59 60 typedef struct { 61 MdDeviceObject DeviceObject; 62 63 // 64 // threadpool wait block 65 // 66 PTP_WAIT WaitBlock; 67 68 HANDLE WorkItemEvent; 69 70 PMX_WORKITEM_ROUTINE Callback; 71 72 PVOID Context; 73 74 // 75 // True if callbacks run in the default thread pool environment, 76 // rather than in an environment explicitly owned by the driver. 77 // This has implications in MxWorkItem::_Free. 78 // 79 BOOLEAN DefaultThreadpoolEnv; 80 } UmWorkItem; 81 82 typedef UmWorkItem* MdWorkItem; 83 84 #include "MxWorkItem.h" 85 86 __inline 87 MxWorkItem::MxWorkItem( 88 ) 89 { 90 m_WorkItem = NULL; 91 } 92 93 _Must_inspect_result_ 94 __inline 95 NTSTATUS 96 MxWorkItem::Allocate( 97 __in MdDeviceObject DeviceObject, 98 __in_opt PVOID ThreadPoolEnv 99 ) 100 { 101 DWORD err = 0; 102 103 m_WorkItem = (MdWorkItem)::HeapAlloc( 104 GetProcessHeap(), 105 0, 106 sizeof(UmWorkItem) 107 ); 108 109 if (NULL == m_WorkItem) { 110 return STATUS_INSUFFICIENT_RESOURCES; 111 } 112 113 ZeroMemory(m_WorkItem, sizeof(UmWorkItem)); 114 115 m_WorkItem->WorkItemEvent = CreateEvent( 116 NULL, 117 FALSE, 118 FALSE, 119 NULL); 120 121 if (NULL == m_WorkItem->WorkItemEvent) { 122 err = GetLastError(); 123 goto exit; 124 } 125 126 m_WorkItem->WaitBlock = CreateThreadpoolWait( 127 _WorkerThunk, 128 this->GetWorkItem(), // Context to callback function 129 (PTP_CALLBACK_ENVIRON)ThreadPoolEnv 130 ); 131 132 if (m_WorkItem->WaitBlock == NULL) { 133 err = GetLastError(); 134 goto exit; 135 } 136 137 m_WorkItem->DefaultThreadpoolEnv = (NULL == ThreadPoolEnv); 138 139 m_WorkItem->DeviceObject = DeviceObject; 140 141 exit: 142 // 143 // Cleanup in case of failure 144 // 145 if (0 != err) { 146 if (NULL != m_WorkItem->WorkItemEvent) { 147 CloseHandle(m_WorkItem->WorkItemEvent); 148 m_WorkItem->WorkItemEvent = NULL; 149 } 150 151 ::HeapFree(GetProcessHeap(), 0, m_WorkItem); 152 m_WorkItem = NULL; 153 } 154 155 return NTSTATUS_FROM_WIN32(err); 156 } 157 158 __inline 159 VOID 160 MxWorkItem::Enqueue( 161 __in PMX_WORKITEM_ROUTINE Callback, 162 __in PVOID Context 163 ) 164 { 165 // 166 // ASSUMPTION: This function assumes that another call to Enqueue 167 // is made only after the callback has been invoked, altough it is OK 168 // to make another call from within the callback. 169 // 170 // It is up to a higher layer/caller to ensure this. 171 // For example: FxSystemWorkItem layered on top of MxWorkItem ensures this. 172 // 173 174 // 175 // Since multiple calls to Enqueue cannot be made at the same time 176 // as explained above, it is OK to store callback and context in 177 // the workitem itself. 178 // 179 // This behavior is similar to that of IoQueueWorkItem which accepts 180 // a callback and a context which are stored within the work-item. 181 // 182 183 m_WorkItem->Callback = Callback; 184 m_WorkItem->Context = Context; 185 186 // 187 // We must register the event with the wait object before signaling it 188 // to trigger the wait callback. 189 // 190 SetThreadpoolWait(m_WorkItem->WaitBlock, 191 m_WorkItem->WorkItemEvent, 192 NULL // timeout 193 ); 194 195 SetEvent(m_WorkItem->WorkItemEvent); 196 } 197 198 __inline 199 MdWorkItem 200 MxWorkItem::GetWorkItem( 201 ) 202 { 203 return m_WorkItem; 204 } 205 206 __inline 207 VOID 208 MxWorkItem::_Free( 209 __in MdWorkItem Item 210 ) 211 { 212 // 213 // PLEASE NOTE that _Free waits for callback to return synchronously. 214 // 215 // DO NOT call _Free from work item callback otherwise it would cause a 216 // deadlock. 217 // 218 // Please see comments on the top of the file. 219 // 220 221 if (NULL != Item) { 222 // 223 // Wait indefinitely for work item to complete 224 // 225 if (NULL != Item->WaitBlock) { 226 // 227 // this will prevent any new waits to be queued but callbacks 228 // already queued will still occur. 229 // 230 SetThreadpoolWait(Item->WaitBlock, NULL, NULL); 231 232 // 233 // If the callbacks ran in the default thread pool environment, 234 // wait for callbacks to finish. 235 // If they ran in an environment explicitly owned by the driver, 236 // then this wait will happen before the driver DLL is unloaded, 237 // the host takes care of this. 238 // 239 if (Item->DefaultThreadpoolEnv) { 240 WaitForThreadpoolWaitCallbacks(Item->WaitBlock, 241 FALSE // donot cancel pending waits 242 ); 243 } 244 245 // 246 // Release the wait object. 247 // 248 CloseThreadpoolWait(Item->WaitBlock); 249 } 250 251 if (NULL != Item->WorkItemEvent) { 252 CloseHandle(Item->WorkItemEvent); 253 Item->WorkItemEvent = NULL; 254 } 255 256 ::HeapFree( 257 GetProcessHeap(), 258 0, 259 Item 260 ); 261 } 262 } 263 264 __inline 265 VOID 266 MxWorkItem::Free( 267 ) 268 { 269 // 270 // PLEASE NOTE that _Free waits for callback to return synchronously. 271 // 272 // DO NOT call Free from work item callback otherwise it would cause a 273 // deadlock. 274 // 275 // Please see comments on the top of the file. 276 // 277 278 if (NULL != m_WorkItem) { 279 MxWorkItem::_Free(m_WorkItem); 280 m_WorkItem = NULL; 281 } 282 } 283 284 // 285 // FxAutoWorkitem 286 // 287 __inline 288 MxAutoWorkItem::~MxAutoWorkItem( 289 ) 290 { 291 this->Free(); 292 } 293 294 295