/***************************************************************************** * * DIEmH.c * * Copyright (c) 1996 Microsoft Corporation. All Rights Reserved. * * Abstract: * * Emulation module for HID. HID is always run at ring 3, * so "emulation" is a bit of a misnomer. * * Contents: * * CEm_HID_CreateInstance * *****************************************************************************/ #include "dinputpr.h" #ifdef HID_SUPPORT /***************************************************************************** * * The sqiffle for this file. * *****************************************************************************/ #define sqfl sqflEm /***************************************************************************** * * Forward declarations * * CEm_HID_ReadComplete and CEm_HID_IssueRead schedule each other * back and forth. * *****************************************************************************/ void CALLBACK CEm_HID_ReadComplete(DWORD dwError, DWORD cbRead, LPOVERLAPPED po); /***************************************************************************** * * HID "emulation" * *****************************************************************************/ STDMETHODIMP CEm_HID_Acquire(PEM this, BOOL fAcquire); /***************************************************************************** * * @doc INTERNAL * * @func BOOL | FakeCancelIO | * * Stub function which doesn't do anything but * keeps us from crashing. * * @parm HANDLE | h | * * The handle whose I/O is supposed to be cancelled. * *****************************************************************************/ BOOL WINAPI FakeCancelIO(HANDLE h) { AssertF(0); return FALSE; } /***************************************************************************** * * @doc INTERNAL * * @func BOOL | FakeTryEnterCriticalSection | * * We use TryEnterCriticalSection in DEBUG to detect deadlock * If the function does not exist, just enter CritSection and report * true. This compromises some debug functionality. * * @parm LPCRITICAL_SECTION | lpCriticalSection | * * Address of Critical Section to be entered. * *****************************************************************************/ #ifdef XDEBUG BOOL WINAPI FakeTryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) { EnterCriticalSection(lpCriticalSection); return TRUE; } #endif /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_HID_Hold | * * Place a hold on both the parent device and the * emulation structure, so neither will go away while * we aren't paying attention. * * @parm PCHID | this | * * The item to be held. * *****************************************************************************/ void INTERNAL CEm_Hid_Hold(PCHID this) { CEm_AddRef(pemFromPvi(this->pvi)); Common_Hold(this); } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_HID_Unhold | * * Release the holds we placed via . * * @parm PCHID | this | * * The item to be unheld. * *****************************************************************************/ void INTERNAL CEm_Hid_Unhold(PCHID this) { CEm_Release(pemFromPvi(this->pvi)); Common_Unhold(this); } /***************************************************************************** * * @doc EXTERNAL * * @func BOOL | CEm_HID_IssueRead | * * Issue another read request. * * @parm PCHID | this | * * The device on which the read is to be issued. * * @returns * * Returns nonzero if the read was successfully issued. * *****************************************************************************/ BOOL EXTERNAL CEm_HID_IssueRead(PCHID this) { BOOL fRc; fRc = ReadFileEx(this->hdevEm, this->hriIn.pvReport, this->hriIn.cbReport, &this->o, CEm_HID_ReadComplete); if(!fRc) { /* * Couldn't issue read; force an unacquire. * * Unhold the device once, since the read loop is gone. */ // 7/18/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflError, TEXT("IssueRead: Access to HID device(%p, handle=0x%p) lost le=0x%x!"), this, this->hdevEm, GetLastError() ); DllEnterCrit(); ConfirmF(SUCCEEDED(GPA_DeletePtr(&g_plts->gpaHid, pemFromPvi(this->pvi)))); DllLeaveCrit(); CEm_ForceDeviceUnacquire(&this->ed, (!(this->pvi->fl & VIFL_ACQUIRED)) ? FDUFL_UNPLUGGED : 0); CEm_Hid_Unhold(this); // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("Removed HID device(%p) from GPA "), this); } return fRc; } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_HID_PrepareState | * * Prepare the staging area for a new device state * by assuming that nothing has changed. * * @parm PCHID | this | * * The device on which a read has just completed. * *****************************************************************************/ void INLINE CEm_HID_PrepareState(PCHID this) { /* * Copy over everything... */ CopyMemory(this->pvStage, this->pvPhys, this->cbPhys); } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_HID_ReadComplete | * * APC function which is called when an I/O has completed. * * @parm DWORD | dwError | * * Error code, or zero on success. * * @parm DWORD | cbRead | * * Number of bytes actually read. * * @parm LPOVERLAPPED | po | * * I/O packet that completed. * *****************************************************************************/ void CALLBACK CEm_HID_ReadComplete(DWORD dwError, DWORD cbRead, LPOVERLAPPED po) { PCHID this = pchidFromPo(po); //EnterProc(Cem_HID_ReadComplete, (_"ddp", dwError, cbRead, po )); /* * Cannot own any critical sections because CEm_ForceDeviceUnacquire * assumes that no critical sections are taken. */ AssertF(!CDIDev_InCrit(this->pvi->pdd)); AssertF(!DllInCrit()); /* * Process the data. * * Note: We can get error STATUS_DEVICE_NOT_CONNECTED * or ERROR_READ_FAULT if the device is unplugged. */ if(dwError == 0 && this->o.InternalHigh == this->caps.InputReportByteLength) { NTSTATUS stat; CEm_HID_PrepareState(this); stat = CHid_ParseData(this, HidP_Input, &this->hriIn); if(SUCCEEDED(stat)) { CEm_AddState(&this->ed, this->pvStage, GetTickCount()); } CEm_HID_IssueRead(this); } else { if(!dwError) { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqflError | sqfl, TEXT("ReadComplete HID(%p) short read! Got %d wanted %d"), this, this->o.InternalHigh, this->caps.InputReportByteLength); } else { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqflError | sqfl, TEXT("ReadComplete HID(%p) read failed! error=0x%08x "), this, dwError); } DllEnterCrit(); ConfirmF(SUCCEEDED(GPA_DeletePtr(&g_plts->gpaHid, pemFromPvi(this->pvi)))); DllLeaveCrit(); CEm_ForceDeviceUnacquire(&this->ed, (!(this->pvi->fl & VIFL_ACQUIRED)) ? FDUFL_UNPLUGGED : 0); CEm_Hid_Unhold(this); } /* * And wait for more data. * If the read failed, then CEm_HID_IssueRead() will its Reference */ // CEm_HID_IssueRead(this); //ExitProc(); } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_HID_Sync | * * Kick off a read or kill the existing one. * * @parm PLLTHREADSTATE | plts | * * Thread hook state containing hook information to synchronize. * * @parm PEM | pem | * * Who is the poor victim? * *****************************************************************************/ void EXTERNAL CEm_HID_Sync(PLLTHREADSTATE plts, PEM pem) { PCHID this; EnterProc(CEm_HID_Sync, (_ "pp", plts, pem )); this = pchidFromPem(pem); AssertF(GPA_FindPtr(&plts->gpaHid, pem)); AssertF(this->pvi == &pem->vi); AssertF(pem->ped == &this->ed); /* * Cannot own any critical sections because CEm_HID_IssueRead * may result in a call to CEm_ForceDeviceUnacquire, which * in turn assumes that no critical sections are taken. */ AssertF(!CDIDev_InCrit(this->pvi->pdd)); AssertF(!DllInCrit()); if( pem->vi.fl & VIFL_ACQUIRED ) { AssertF(this->hdevEm == INVALID_HANDLE_VALUE); /* * Start reading. * * While underneath the device critical section, duplicate * the handle so we can avoid race conditions with the * main thread (when the main thread closes the handle, * we need to keep our private version alive so we can * clean it up nicely). */ /* * Need to look again, in case the device has already * been unacquired before we get a chance to synchronize * with the main thread. This can happen, for example, * if the app quickly does an Acquire/Unacquire without * an intervening thread switch. */ AssertF(!CDIDev_InCrit(this->pvi->pdd)); //CDIDev_EnterCrit(this->pvi->pdd); if(this->hdev != INVALID_HANDLE_VALUE) { HANDLE hProcessMe = GetCurrentProcess(); HANDLE hdevEm; if(DuplicateHandle(hProcessMe, this->hdev, hProcessMe, &hdevEm, GENERIC_READ, 0, 0)) { this->hdevEm = hdevEm; } } //CDIDev_LeaveCrit(this->pvi->pdd); if(this->hdevEm != INVALID_HANDLE_VALUE) { /* * On Win98, HidD_FlushQueue will fail if the underlying * device is dead. Whereas on NT, it blindly succeeds. * Therefore, we cannot trust the return value. */ HidD_FlushQueue(this->hdevEm); } /* * Even if we have failed to duplicate the handle * we still want to issue the read. A error in read * will force the device to be unacquired */ CEm_HID_IssueRead(this); // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT(" StartReading(%p) "), this); } else { HANDLE hdev; /* * Stop reading. There is still another outstanding * hold by the read loop, which will be cleaned up when * the the I/O cancel is received. */ AssertF(this->hdevEm != INVALID_HANDLE_VALUE); hdev = this->hdevEm; this->hdevEm = INVALID_HANDLE_VALUE; if(hdev != INVALID_HANDLE_VALUE) { /* * We don't need to call CancelIo because we're closing * the handle soon anyway. Which is good, because Memphis * B#55771 prevents CancelIo from working on read-only * handles (which we are). * */ /* Need CancelIo on NT otherwise HID devices appear only on every * consecutive plug in */ _CancelIO(hdev); CloseHandle(hdev); } // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT(" StopReading(%p) "), this); } ExitProc(); } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_HID_Acquire | * * Acquire/unacquire a HID device. * * @parm PEM | pem | * * Device being acquired. * * @parm BOOL | fAcquire | * * Whether the device is being acquired or unacquired. * *****************************************************************************/ STDMETHODIMP CEm_HID_Acquire(PEM pem, BOOL fAcquire) { HRESULT hres; PLLTHREADSTATE plts; PCHID pchid; EnterProc(CEm_HID_Acquire, (_ "pu", pem, fAcquire)); AssertF(pem->dwSignature == CEM_SIGNATURE); pchid = pchidFromPem(pem); if( fAcquire ) { pchid->hdev = CHid_OpenDevicePath(pchid, FILE_FLAG_OVERLAPPED); if(pchid->hdev != INVALID_HANDLE_VALUE ) { hres = S_OK; } else { hres = DIERR_UNPLUGGED; } } else { HANDLE hdev; AssertF(pchid->hdev != INVALID_HANDLE_VALUE); hdev = pchid->hdev; pchid->hdev = INVALID_HANDLE_VALUE; _CancelIO(hdev); CloseHandle(hdev); hres = S_OK; } if( pchid->IsPolledInput ) { hres = S_OK; AssertF(pchid->hdevEm == INVALID_HANDLE_VALUE); } else if( SUCCEEDED(hres) ) { #ifdef USE_WM_INPUT ResetEvent( g_hEventHid ); #endif hres = CEm_GetWorkerThread(pem, &plts); if(SUCCEEDED(hres) ) { if(fAcquire ) { /* Begin the I/O */ /* * Must apply the hold before adding to the list * to avoid a race condition where the worker thread * unholds the pchid before we can hold it. * * The rule is that there is a hold to track each copy * of the device on the gpaHid. */ CEm_Hid_Hold(pchid); /* * Add ourselves to the busy list, and wake up * the worker thread to tell him to start paying attention. */ DllEnterCrit(); hres = GPA_Append(&plts->gpaHid, pem); DllLeaveCrit(); // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("Added HID device(%p) to GPA "), pchid ); if(FAILED(hres)) { CEm_Hid_Unhold(pchid); } NudgeWorkerThreadPem(plts, pem); #ifdef USE_WM_INPUT if( g_fRawInput) { DWORD dwRc; dwRc = WaitForSingleObject( g_hEventHid, INFINITE ); } #endif } else { HANDLE hdev; hdev = pchid->hdevEm; pchid->hdevEm = INVALID_HANDLE_VALUE; if(hdev != INVALID_HANDLE_VALUE) { _CancelIO(hdev); CloseHandle(hdev); } } } } ExitOleProc(); return hres; } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_HID_CreateInstance | * * Create a HID thing. * * @parm PVXDDEVICEFORMAT | pdevf | * * What the object should look like. * * @parm PVXDINSTANCE * | ppviOut | * * The answer goes here. * *****************************************************************************/ HRESULT EXTERNAL CEm_HID_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut) { PCHID pchid = (PCHID)pdevf->dwExtra; PED ped = &pchid->ed; AssertF(ped->pState == 0); AssertF(ped->pDevType == 0); *(PPV)&ped->pState = pchid->pvPhys; /* De-const */ ped->Acquire = CEm_HID_Acquire; ped->cAcquire = -1; ped->cbData = pdevf->cbData; ped->cRef = 0x0; return CEm_CreateInstance(pdevf, ppviOut, &pchid->ed); } #endif /* HID_SUPPORT */