How do I correlate a sound device with an HID device on that sound device

Corey Minyard 66 Reputation points
2023-04-14T16:07:43.14+00:00

I have a USB device with a CM108 audio chip on it. That chip has GPIOs on it that I need to use to accomplish certain actions on the board. Using the multimedia API I can easily find the sound card, of course. And I can use the setup api (setupapi.h) to find the HID device with the GPIOs, and I can control the GPIOs without issue.

What I have not figured out, however, is how to correlate the sound card with the HID device. What I would like is to find the sound device, extract some kind of path or identifier, then go through the HID devices and match up that identifier. Or a more direct method would be fine, too, I just need some way to say "This sound device has this HID device on it." I haven't found a way to do this.

Also, the manufacturer and product ids in the list of sound devices you get from waveOutGetDevCaps() are both set to 0xffff for USB devices, it appears. Why isn't it the vendor and product from the USB device? That's not a great solution for matching things up because you can't handle multiple devices of the same type, but it's strange that the numbers aren't set correctly.

Windows for business | Windows Client for IT Pros | Devices and deployment | Other
{count} votes

3 answers

Sort by: Most helpful
  1. Castorix31 90,686 Reputation points
    2023-04-17T11:24:58.2433333+00:00

    I made a test by listing HID and Audio Renderer devices with SetupDi* APIs with a few properties that you could compare :

    GUID Guid;
    HidD_GetHidGuid(&Guid);
    SetupDiEnumDevices(&Guid);
    const GUID KSCATEGORY_RENDER = { 0x65E8773E, 0x8F56, 0x11D0, {0xA3, 0xB9, 0x00, 0xA0, 0xC9, 0x22, 0x31, 0x96} };
    const GUID KSCATEGORY_AUDIO = { 0x6994AD04, 0x93EF, 0x11D0, 0xA3, 0xCC,0x00, 0xA0,0xC9,0x22, 0x31, 0x96 };
    SetupDiEnumDevices(&KSCATEGORY_RENDER);
    //SetupDiEnumDevices(&KSCATEGORY_AUDIO);
    
    

    with :

    
    void GetErrorMessage(HRESULT hr, __out_opt WCHAR* pwsMessage)
    {
    	LPVOID lpMsgBuf = NULL;
    
    	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
    		FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
    		(HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : hr,
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPWSTR)&lpMsgBuf, 0, NULL);
    
    	if (lpMsgBuf != NULL)
    	{
    		wsprintf(pwsMessage, L"%s", lpMsgBuf);
    		LocalFree(lpMsgBuf);
    	}
    }
    
    void SetupDiEnumDevices(CONST GUID* pClassGuid)
    {
    	HDEVINFO hDI = SetupDiGetClassDevs(pClassGuid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
    	if (INVALID_HANDLE_VALUE != hDI)
    	{
    		SP_DEVINFO_DATA DeviceInfoData;
    		DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    		for (DWORD dwDeviceIndex = 0; SetupDiEnumDeviceInfo(hDI, dwDeviceIndex, &DeviceInfoData); dwDeviceIndex++)
    		{
    			SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
    			DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    			for (DWORD dwMemberIndex = 0; SetupDiEnumDeviceInterfaces(hDI, &DeviceInfoData, pClassGuid, dwMemberIndex, &DeviceInterfaceData); dwMemberIndex++)
    			{
    				DWORD dwDeviceInterfaceDetailDataSize = offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath) + MAX_PATH * sizeof(TCHAR);
    				PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)new BYTE[dwDeviceInterfaceDetailDataSize];
    				pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
    				if (SetupDiGetDeviceInterfaceDetail(hDI, &DeviceInterfaceData, pDeviceInterfaceDetailData, dwDeviceInterfaceDetailDataSize, NULL, NULL))
    				{
    					CONFIGRET cRet;
    					WCHAR wsText[MAX_PATH] = L"";
    					DEVINST hDevInst = DeviceInfoData.DevInst;
    					DEVINST hParentDevInst;
    					TCHAR wsDeviceID[MAX_DEVICE_ID_LEN];
    
    					DWORD dwRegType;
    					WCHAR wsPropertyBuffer[MAX_PATH] = L"";
    					BOOL bRet = SetupDiGetDeviceRegistryProperty(hDI, &DeviceInfoData, SPDRP_FRIENDLYNAME, &dwRegType, (BYTE*)wsPropertyBuffer, MAX_PATH, NULL);
    					if (bRet)
    					{
    						wsprintf(wsText, L"Name : %s\r\n", wsPropertyBuffer);
    						OutputDebugString(wsText);
    					}
    					else
    					{
    						bRet = SetupDiGetDeviceRegistryProperty(hDI, &DeviceInfoData, SPDRP_DEVICEDESC, &dwRegType, (BYTE*)wsPropertyBuffer, MAX_PATH, NULL);
    						if (bRet)
    						{
    							wsprintf(wsText, L"Name (Description) : %s\r\n", wsPropertyBuffer);
    							OutputDebugString(wsText);
    						}
    					}
    
    					cRet = CM_Get_Device_ID(hDevInst, wsDeviceID, sizeof(wsDeviceID) / sizeof(TCHAR), 0);
    					if (cRet == CR_SUCCESS)
    					{
    						wsprintf(wsText, L"\tDeviceID : %s\r\n", wsDeviceID);
    						OutputDebugString(wsText);
    
    						wsprintf(wsText, L"\tDevice Path : %s\r\n", pDeviceInterfaceDetailData->DevicePath);
    						OutputDebugString(wsText);
    					}
    					cRet = CM_Get_Parent(&hParentDevInst, hDevInst, 0);
    					if (cRet == CR_SUCCESS)
    					{
    						TCHAR wsParentDeviceID[MAX_DEVICE_ID_LEN];
    						cRet = CM_Get_Device_ID(hParentDevInst, wsParentDeviceID, sizeof(wsParentDeviceID) / sizeof(TCHAR), 0);
    						if (cRet == CR_SUCCESS)
    						{
    							wsprintf(wsText, L"\tParent DeviceID : %s\r\n", wsParentDeviceID);
    							OutputDebugString(wsText);
    						}
    					}
    
    					DEVPROPTYPE PropertyType;
    					WCHAR wsDevNodeProperty[MAX_PATH] = L"";
    					DWORD nSize = 0;
    					cRet = CM_Get_DevNode_Property(hDevInst, &DEVPKEY_Device_ContainerId, &PropertyType, NULL, &nSize, 0);
    					if (cRet == CR_BUFFER_SMALL)
    					{
    						cRet = CM_Get_DevNode_Property(hDevInst, &DEVPKEY_Device_ContainerId, &PropertyType, (PBYTE)wsDevNodeProperty, &nSize, 0);
    					}
    					if (cRet == CR_SUCCESS)
    					{
    						WCHAR wsBuffer[4096];
    						StringFromGUID2((REFGUID)wsDevNodeProperty, wsBuffer, ARRAYSIZE(wsBuffer));
    						wsprintf(wsText, L"\tContainer Id : %s\r\n", wsBuffer);
    						OutputDebugString(wsText);
    					}
    
    					PropertyType = NULL;
    					nSize = 0;
    					cRet = CM_Get_DevNode_Property(hDevInst, &DEVPKEY_Device_Manufacturer, &PropertyType, NULL, &nSize, 0);
    					if (cRet == CR_BUFFER_SMALL)
    					{
    						cRet = CM_Get_DevNode_Property(hDevInst, &DEVPKEY_Device_Manufacturer, &PropertyType, (PBYTE)wsDevNodeProperty, &nSize, 0);
    					}
    					if (cRet == CR_SUCCESS)
    					{
    						wsprintf(wsText, L"\tManufacturer : %s\r\n", wsDevNodeProperty);
    						OutputDebugString(wsText);
    					}
    
    					HANDLE hFile = CreateFile(pDeviceInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    					if (hFile != INVALID_HANDLE_VALUE)
    					{
    						WCHAR wsVendorString[MAX_PATH] = L"";
    						HIDD_ATTRIBUTES attributes;
    						attributes.Size = sizeof(HIDD_ATTRIBUTES);
    						BOOL bRet = HidD_GetAttributes(hFile, &attributes);
    						if (bRet)
    						{
    							// https://github.com/microsoft/Windows-driver-samples/blob/main/usb/usbview/vndrlist.h		
    							switch (attributes.VendorID)
    							{
    							case 0x045E:
    							{
    								lstrcpy(wsVendorString, L"Microsoft Corporation");
    							}
    							break;
    							case 0x0955:
    							{
    								lstrcpy(wsVendorString, L"NVIDIA");
    							}
    							break;
    							case 0x0D8C:
    							{
    								lstrcpy(wsVendorString, L"C-Media Electronics, Inc.");
    							}
    							break;
    							default:
    								wsprintf(wsVendorString, L"0x%4X", attributes.VendorID);
    							}
    							wsprintf(wsText, L"\tHid Vendor : %s\r\n", wsVendorString);
    							OutputDebugString(wsText);
    						}
    						CloseHandle(hFile);
    					}
    					else
    					{
    						DWORD nError = GetLastError();
    						WCHAR wsMessage[MAX_PATH] = L"";
    						GetErrorMessage(nError, wsMessage);
    						wsprintf(wsText, L"\tCreateFile Error : %d = %s\r\n", nError, wsMessage);
    						OutputDebugString(wsText);
    					}
    				}
    				delete[] pDeviceInterfaceDetailData;
    			}
    		}
    		SetupDiDestroyDeviceInfoList(hDI);
    	}
    }
    

  2. Corey Minyard 66 Reputation points
    2023-04-17T23:41:42.8766667+00:00

    It does look like the container id is the right way to go. From https://learn.microsoft.com/en-us/windows-hardware/drivers/install/container-ids :

    A container ID is a system-supplied device identification string that uniquely groups the functional devices associated with a single-function or multifunction device installed in the computer.

    Seems perfect. You could use the hardware id, it appears, as there is a section that looks to be the same, but from https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-identification-strings :

    Device identification strings should not be parsed. They are meant only for string comparisons and should be treated as opaque strings.

    So you iterate the devices using SetupDiGetClassDevsA() and SetupDiEnumDeviceInterfaces(), then you can enumerate the devices and fetch the container id with SetupDiGetDevicePropertyW(). I couldn't get DEVPKEY_Device_ContainerId to work, so I hacked it in myself. The definition is there, but I couldn't find what library to include to get the implementation. So thanks for you help.


  3. Corey Minyard 66 Reputation points
    2023-04-18T23:56:37.5033333+00:00

    I finally have a solution for this. Since ContainerId didn't work, I went with the parent-child path. I iterate through the devices and find the soundcard with the FriendlyName field, then I search up through parents until I find a USB Container, then return the ID for that. Then I iterate through the devices again until I find a HIDClass device with the same USB Container parent. Note that the capitalization of the device name varied for some strange reason, so I had to upcase everything to make it work. You can find working code at https://github.com/cminyard/gensio/blob/master/lib/gensio_cm108gpio.c and a Linux implementation to do the same thing.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.