summaryrefslogtreecommitdiff
path: root/audio/tests/PinResourceHelpers/PropertyHelper.cpp
diff options
context:
space:
mode:
authorDaniel Rugerio <[email protected]>2021-08-13 15:05:12 -0700
committerGitHub <[email protected]>2021-08-13 15:05:12 -0700
commit7895dd22785ddba5e973662ed942be3b3452b89d (patch)
tree5c4572d5bfd076d3776a88c5757e37f3ed50c0e7 /audio/tests/PinResourceHelpers/PropertyHelper.cpp
parentdf47b2d284558fa9aacd19257153037e4ebba60e (diff)
audio: Updates for Windows 10 22000 (#655)
* Updates for Windows 10 22000. * Change how WIL is consumed.
Diffstat (limited to 'audio/tests/PinResourceHelpers/PropertyHelper.cpp')
-rw-r--r--audio/tests/PinResourceHelpers/PropertyHelper.cpp486
1 files changed, 483 insertions, 3 deletions
diff --git a/audio/tests/PinResourceHelpers/PropertyHelper.cpp b/audio/tests/PinResourceHelpers/PropertyHelper.cpp
index 6e85e53e..1d5f4548 100644
--- a/audio/tests/PinResourceHelpers/PropertyHelper.cpp
+++ b/audio/tests/PinResourceHelpers/PropertyHelper.cpp
@@ -1,6 +1,6 @@
// ------------------------------------------------------------------------------
//
-// Copyright (C) Microsoft. All rights reserved.
+// Copyright (C) Microsoft Corporation. All rights reserved.
//
// Module Name:
//
@@ -16,6 +16,11 @@
#include <Functiondiscoverykeys_devpkey.h>
#include <devpkey.h>
#include <PropertyHelper.h>
+#include <imagehlp.h>
+
+using namespace WEX::Common;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
@@ -1099,6 +1104,355 @@ HRESULT GetAvailiablePinInstanceCount
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
+// GetDriverPathViaService
+//
+// Get the full path to the driver .sys file by the service name
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetDriverPathViaService
+(
+ LPWSTR ServiceName,
+ LPWSTR DriverFullPath,
+ UINT cchFullPath
+)
+{
+ HRESULT hr = S_OK;
+ wil::unique_schandle scManager;
+ wil::unique_schandle scService;
+
+ scManager = wil::unique_schandle(OpenSCManagerW(nullptr, nullptr, GENERIC_READ));
+ if (!VERIFY_IS_NOT_NULL(scManager.get()))
+ {
+ hr = E_OUTOFMEMORY;
+ return hr;
+ }
+
+ scService = wil::unique_schandle(OpenServiceW(scManager.get(), ServiceName, GENERIC_READ));
+ if (!VERIFY_IS_NOT_NULL(scService.get()))
+ {
+ hr = E_OUTOFMEMORY;
+ return hr;
+ }
+
+ DWORD BytesRequired;
+ if (!VERIFY_IS_TRUE(!QueryServiceConfigW(scService.get(), nullptr, 0, &BytesRequired) && ERROR_INSUFFICIENT_BUFFER == GetLastError()))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ wil::unique_cotaskmem_ptr<BYTE> pBuff;
+ pBuff.reset((BYTE*)CoTaskMemAlloc(BytesRequired));
+ if (!VERIFY_IS_NOT_NULL(pBuff))
+ {
+ hr = E_OUTOFMEMORY;
+ return hr;
+ }
+
+ LPQUERY_SERVICE_CONFIGW ServiceConfig = (LPQUERY_SERVICE_CONFIGW)pBuff.get();
+ DWORD BytesReturned;
+
+ if (!VERIFY_IS_TRUE(QueryServiceConfigW(scService.get(), ServiceConfig, BytesRequired, &BytesReturned)))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ if (!VERIFY_IS_NOT_NULL(ServiceConfig->lpBinaryPathName))
+ {
+ hr = E_UNEXPECTED;
+ return hr;
+ }
+
+ if (!VERIFY_IS_TRUE('\0' != ServiceConfig->lpBinaryPathName[0]))
+ {
+ hr = E_UNEXPECTED;
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = GetFullPathFromImagePath(ServiceConfig->lpBinaryPathName, DriverFullPath, cchFullPath)))
+ {
+ return hr;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// GetFullPathFromImagePath
+//
+// Get the full driver path from the image path
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT GetFullPathFromImagePath
+(
+ LPWSTR ImagePath,
+ LPWSTR DriverFullPath,
+ UINT cchFullPath
+)
+{
+ HRESULT hr = S_OK;
+ //
+ // First, check if the ImagePath uses either of the well-known kernel
+ // DosDevices prefixes. If so, skip over those first since
+ // GetFileAttributes would succeed, leading us to think it is a valid DOS
+ // path, and GetFullPathName handles these incorrectly anyways. Don't
+ // bother handling the user DosDevices formats since it would be invalid to
+ // specify that format for the ImagePath of a kernel module.
+ //
+ LPWSTR pImagePath = (LPWSTR)ImagePath;
+
+ const wchar_t* DosDevicesPath = L"\\DosDevices\\";
+ const wchar_t* QuestionPath = L"\\??\\";
+ if (_wcsnicmp(pImagePath,
+ DosDevicesPath,
+ static_cast<int>(wcslen(DosDevicesPath))) == 0)
+ {
+ pImagePath += wcslen(DosDevicesPath);
+ }
+ else if (_wcsnicmp(pImagePath,
+ QuestionPath,
+ static_cast<int>(wcslen(QuestionPath))) == 0)
+ {
+ pImagePath += wcslen(QuestionPath);
+ }
+
+ //
+ // Check if the ImagePath happens to be a valid full path.
+ //
+ if (GetFileAttributesW(pImagePath) != 0xFFFFFFFF)
+ {
+ if (!VERIFY_IS_TRUE(GetFullPathNameW(pImagePath, cchFullPath, DriverFullPath, nullptr)))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+ return hr;
+ }
+
+ //
+ // If the ImagePath starts with "\SystemRoot" or "%SystemRoot%" then
+ // remove those values.
+ //
+ LPWSTR pRelativePath = (LPWSTR)pImagePath;
+
+ const wchar_t* SystemRootPath = L"\\SystemRoot\\";
+ const wchar_t* SystemRootVariablePath = L"%SystemRoot%\\";
+
+ if (_wcsnicmp(pRelativePath,
+ SystemRootPath,
+ static_cast<int>(wcslen(SystemRootPath))) == 0)
+ {
+ pRelativePath += wcslen(SystemRootPath);
+ }
+ else if (_wcsnicmp(pRelativePath,
+ SystemRootVariablePath,
+ static_cast<int>(wcslen(SystemRootVariablePath))) == 0)
+ {
+ pRelativePath += wcslen(SystemRootVariablePath);
+ }
+
+ //
+ // At this point pRelativePath should point to the image path relative to
+ // the windows directory.
+ //
+ WCHAR WindowsPath[MAX_PATH];
+ if (!VERIFY_IS_TRUE(GetSystemWindowsDirectoryW(WindowsPath, MAX_PATH)))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = StringCchPrintfW(DriverFullPath, cchFullPath, L"%s\\%s", WindowsPath, pRelativePath)))
+ {
+ return hr;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// CheckImports
+//
+// Check if the driver imports a specific module
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT CheckImports
+(
+ LPWSTR DriverPath,
+ LPSTR ModuleNameToCheck,
+ LPSTR MethodNameToCheck,
+ bool* pIsImported
+)
+{
+ HRESULT hr = S_OK;
+
+ *pIsImported = FALSE;
+
+ CHAR szDriverPath[MAX_PATH];
+ size_t bytesConverted = 0;
+ if (!VERIFY_IS_TRUE(0 == wcstombs_s(&bytesConverted, szDriverPath, MAX_PATH, DriverPath, _TRUNCATE)))
+ {
+ hr = E_FAIL;
+ return hr;
+ }
+
+ LOADED_IMAGE image;
+ //Load the image
+ if (!VERIFY_IS_TRUE(MapAndLoad(szDriverPath, NULL, &image, TRUE, TRUE)))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ auto imageCleanup = wil::scope_exit([&]()
+ {
+ (VOID)UnMapAndLoad(&image);
+ });
+
+ //Get the Import Directory
+ ULONG importDescriptorSize;
+ PIMAGE_SECTION_HEADER sectionHeader;
+ PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(image.MappedAddress,
+ FALSE,
+ IMAGE_DIRECTORY_ENTRY_IMPORT,
+ &importDescriptorSize,
+ &sectionHeader);
+ if (!VERIFY_IS_NOT_NULL(importDescriptor))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ return hr;
+ }
+
+ //Iterate through each directory entry
+ while (!(importDescriptor->Characteristics == 0 &&
+ importDescriptor->TimeDateStamp == 0 &&
+ importDescriptor->ForwarderChain == 0 &&
+ importDescriptor->Name == 0 &&
+ importDescriptor->FirstThunk == 0))
+ {
+ CHAR* str = (PCHAR)ImageRvaToVa(ImageNtHeader(image.MappedAddress),
+ image.MappedAddress,
+ importDescriptor->Name,
+ NULL);
+ if (!str || !*str)
+ {
+ continue;
+ }
+
+ if (_stricmp(str, ModuleNameToCheck) == 0)
+ {
+ // If a specific method name is provided, check for the specific method
+ if (MethodNameToCheck)
+ {
+ PIMAGE_THUNK_DATA thunk;
+ //Iterate through the INT(Import Name Table)
+ if (importDescriptor->OriginalFirstThunk == 0)
+ {
+ thunk = nullptr;
+ }
+ else
+ {
+ thunk = (PIMAGE_THUNK_DATA)ImageRvaToVa(ImageNtHeader(image.MappedAddress),
+ image.MappedAddress,
+ importDescriptor->OriginalFirstThunk,
+ NULL);
+ }
+
+ while (thunk && thunk->u1.Ordinal != 0)
+ {
+ if (!IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
+ {
+ PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(ImageNtHeader(image.MappedAddress),
+ image.MappedAddress,
+ (ULONG)thunk->u1.ForwarderString,
+ NULL);
+ if (importName && _stricmp(importName->Name, MethodNameToCheck) == 0)
+ {
+ *pIsImported = TRUE;
+ }
+ }
+
+ thunk++;
+ }
+ }
+ else
+ {
+ *pIsImported = TRUE;
+ }
+
+ break;
+ }
+
+ importDescriptor++;
+ }
+
+ return hr;
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// IsPortCls
+//
+// Check if audio device is PortCls
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT IsPortCls
+(
+ IMMDevice* pDevice,
+ bool* pIsPortCls
+)
+{
+ HRESULT hr = S_OK;
+ wil::com_ptr_nothrow<IMMDevice> spDevnodeDevice;
+ wil::com_ptr_nothrow<IPropertyStore> spPnpProperties;
+ wil::unique_prop_variant varDeviceService;
+
+ *pIsPortCls = false;
+
+ // Find the associated PnP device
+ if (!VERIFY_SUCCEEDED(hr = GetPnpDevnodeFromMMDevice(pDevice, &spDevnodeDevice))) {
+ return hr;
+ }
+
+ // Open pnp device property store
+ if (!VERIFY_SUCCEEDED(hr = spDevnodeDevice->OpenPropertyStore(STGM_READ, &spPnpProperties))) {
+ return hr;
+ }
+
+ // Read the DEVPKEY_Device_Service
+ if (!VERIFY_SUCCEEDED(hr = spPnpProperties->GetValue((REFPROPERTYKEY)DEVPKEY_Device_Service, &varDeviceService))) {
+ return hr;
+ }
+
+ // If DEVPKEY_Device_Service is empty, it likely means a raw PDO, which means the device is handled by the parent FDO.
+ // For practical purposes, this would most likely mean a non-PortCls pin, which is what we were trying to determine, so it is OK.
+ if (varDeviceService.vt == VT_EMPTY) {
+ return S_OK;
+ }
+
+ if (!VERIFY_IS_TRUE(varDeviceService.vt == VT_LPWSTR && varDeviceService.pwszVal != nullptr)) {
+ hr = E_FAIL;
+ return hr;
+ }
+
+ WCHAR FullPath[MAX_PATH];
+ if (!VERIFY_SUCCEEDED(hr = GetDriverPathViaService(varDeviceService.pwszVal, FullPath, MAX_PATH))) {
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = CheckImports(FullPath, "portcls.sys", "PcNewPort", pIsPortCls))) {
+ return hr;
+ }
+
+ return hr;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
// IsAVStream
//
// Check if audio device is AVStream
@@ -1132,13 +1486,24 @@ HRESULT IsAVStream
return hr;
}
+ // If DEVPKEY_Device_Service is empty, it likely means a raw PDO, which means the device is handled by the parent FDO.
+ // For practical purposes, this would most likely mean a non-AVStream pin, which is what we were trying to determine, so it is OK.
+ if (varDeviceService.vt == VT_EMPTY) {
+ return S_OK;
+ }
+
if (!VERIFY_IS_TRUE(varDeviceService.vt == VT_LPWSTR && varDeviceService.pwszVal != nullptr)) {
hr = E_FAIL;
return hr;
}
- if (0 == _wcsicmp(varDeviceService.pwszVal, L"usbaudio") || 0 == _wcsicmp(varDeviceService.pwszVal, L"BthHFAud") || 0 == _wcsicmp(varDeviceService.pwszVal, L"BthA2dp")) {
- *pIsAVStream = true;
+ WCHAR FullPath[MAX_PATH];
+ if (!VERIFY_SUCCEEDED(hr = GetDriverPathViaService(varDeviceService.pwszVal, FullPath, MAX_PATH))) {
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = CheckImports(FullPath, "ks.sys", nullptr, pIsAVStream))) {
+ return hr;
}
return hr;
@@ -1222,6 +1587,78 @@ HRESULT IsSideband
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
+// IsMVA
+//
+// Check if audio device is side band
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+HRESULT IsMVA
+(
+ EndpointConnectorType eConnectorType,
+ IMMDevice* pDevice,
+ bool* pIsMVA
+)
+{
+ HRESULT hr = S_OK;
+ wil::com_ptr_nothrow<IMMDevice> adapterDevice;
+ wil::com_ptr_nothrow<IKsControl> ksControl;
+ ULONG ulReturned = 0;
+ ULONG propertySupportFlags = 0;
+ KSSOUNDDETECTORPROPERTY vam2Property = {0};
+
+ if (!VERIFY_IS_TRUE(pDevice != nullptr)) {
+ hr = E_FAIL;
+ return hr;
+ }
+
+ if (!VERIFY_IS_TRUE(pIsMVA != nullptr)) {
+ hr = E_FAIL;
+ return hr;
+ }
+
+ // If it isn't a keyword connector, it's not MVA, we're done.
+ if (eConnectorType != eKeywordDetectorConnector)
+ {
+ *pIsMVA = FALSE;
+ return S_OK;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = GetAudioFilterAsDevice(pDevice, &adapterDevice)))
+ {
+ return hr;
+ }
+
+ if (!VERIFY_SUCCEEDED(hr = adapterDevice->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (VOID**) &ksControl))) {
+ return hr;
+ }
+
+ // At this point we know that it is a keyword detector connector and we have the required interface to call into the driver
+ // to see if it's MVA. If it's not MVA, it's SVA, so mark it as SVA here.
+ *pIsMVA = false;
+
+ vam2Property.Property.Set = KSPROPSETID_SoundDetector2;
+ vam2Property.Property.Id = KSPROPERTY_SOUNDDETECTOR_SUPPORTEDPATTERNS;
+ vam2Property.Property.Flags = KSPROPERTY_TYPE_BASICSUPPORT;
+ vam2Property.EventId = GUID_NULL;
+
+ hr = ksControl->KsProperty(
+ (PKSPROPERTY)&vam2Property,
+ sizeof(vam2Property),
+ &propertySupportFlags,
+ sizeof(propertySupportFlags),
+ &ulReturned);
+ if (SUCCEEDED(hr) &&
+ propertySupportFlags == (KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_BASICSUPPORT))
+ {
+ // Success, we've identified this as a connector which supports MVA, mark it.
+ *pIsMVA = true;
+ }
+
+ return S_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
// GetPnpDevnodeFromMMDeivce
//
// Get pnp devnode from mm device
@@ -1261,3 +1698,46 @@ HRESULT GetPnpDevnodeFromMMDevice
return hr;
}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// VerifyAllEndpointsPluggedIn
+//
+// Verify there is no unplugged endpoint
+//
+///////////////////////////////////////////////////////////////////////////////////////////////////////////
+void VerifyAllEndpointsPluggedIn()
+{
+ wil::com_ptr_nothrow<IMMDeviceEnumerator> spEnumerator;
+ wil::com_ptr_nothrow<IMMDeviceCollection> spEndpoints;
+ UINT cDevices = 0;
+
+ SetVerifyOutput verifySettings(VerifyOutputSettings::LogOnlyFailures);
+ DisableVerifyExceptions disable;
+
+ VERIFY_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+ // Create IMMDevice Enumerator
+ VERIFY_SUCCEEDED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void **)&spEnumerator));
+
+ // Enumerate all unplugged endpoints
+ VERIFY_SUCCEEDED(spEnumerator->EnumAudioEndpoints(eAll, DEVICE_STATE_UNPLUGGED, &spEndpoints));
+ VERIFY_SUCCEEDED(spEndpoints->GetCount(&cDevices));
+
+ if (!VERIFY_IS_TRUE(cDevices == 0))
+ {
+ Log::Comment(L"Following unplugged audio device(s) found, please plug in unplugged device(s).");
+
+ for (UINT i = 0; i < cDevices; i++) {
+ wil::com_ptr_nothrow<IMMDevice> spEndpoint;
+ wil::unique_cotaskmem_string id;
+ wil::unique_cotaskmem_string friendlyName;
+
+ VERIFY_SUCCEEDED(spEndpoints->Item(i, &spEndpoint));
+ VERIFY_SUCCEEDED(spEndpoint->GetId(&id));
+ VERIFY_SUCCEEDED(GetEndpointFriendlyName(spEndpoint.get(), &friendlyName));
+
+ Log::Comment(String().Format(L"Device: %s (%s) is unplugged.", friendlyName.get(), id.get()));
+ }
+ }
+}