short version: in the system I am testing the USB devices and cabling should always be connected at the same connectors, so when viewed in the USBview application the USB tree should always look the same. but since I will not have the info to identify the devices from that tree I still won't be able to tell if device X is actually connected at the spot for X. however, I can make device X start sending input messages. so I'd like to be able to verify that all the devices and cabling are connected properly from input messages generated by the USB devices.
long version with more details: I want to test if all USB cabling is properly connected to pre-specified connectors in a system. to do this properly I need info on the ports to which the USB input devices in the system are connected. I know this is doable because I have debugged the USBview sample application (it can be found here). unfortunately I will have no knowledge of the connected devices beforehand so I can only test on port numbers and let the devices generate input messages to help me check if the cabling is connected properly. in order to do this I need to find out where the generated message is from (it's device location information). this is where I get lost.
I have subscribed to receiving WM_INPUT messages from keyboards and mice and I get those. I also get the location information of the device that generated the message by getting the raw device name (or path, more info here) from the message and using that to lookup the Location Information in the registry from HKLM\SYSTEM\CurrentControlSet\Enum\USB
. to find the Location Information I first find the subkey named with the input device's hardware ID (vendor ID or VID and product ID or PID) that is also part of the raw device path and then enumerate all its subkeys (instance IDs) until I find one that features a ParentIdPrefix
with a value that matches the instance ID that is also part of the raw device path. for that subkey I lookup the value of LocationInformation
(formatted Port_#000X.Hub_#000Y
). this works for keyboards and mice attached to my laptop or to my docking station, the port and hub numbers I get from input messages are consistent and reliable even when re-attaching the devices in random order, but it stops being consistent and reliable when I add USB hubs in between. the hub numbers seem to depend on the order in which the hubs are connected to the system, for example connecting A first and B next results in Port_#0001.Hub_#0004 for A and Port_#0001.Hub_#0005 for B but connecting them the other way around results in Port_#0001.Hub_#0005 for A and Port_#0001.Hub_#0004 for B (that's the location information my application reports when next receiving their input messages). the USBview sample application reports consistent hub and port numbers for these devices though (even with re-attaching and restarting), so something in my lookup of location information must be wrong.. but what? apparently I cannot rely on the registry alone to get the location information (I know USBview uses SetupDi* calls and its own enumeration routines). so how do I reliably find the location information like that in USBview corresponding to the device that generated the WM_INPUT message? for example, can I match the raw input device handle that I get in the WM_INPUT message to anything that I can then use to get the location information like USBview does?
here is the code I have so far...
... in InitInstance:
// register for raw input device input messages
RAWINPUTDEVICE rid[2];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06; // keyboard
rid[0].dwFlags =
RIDEV_DEVNOTIFY | // receive device arrival / removal messages
RIDEV_INPUTSINK; // receive messages even if not in foreground
rid[0].hwndTarget = hWnd;
rid[1].usUsagePage = 0x01;
rid[1].usUsage = 0x02; // mouse
rid[1].dwFlags =
RIDEV_DEVNOTIFY |
RIDEV_INPUTSINK;
rid[1].hwndTarget = hWnd;
if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE)
{
DisplayLastError(TEXT("Failed to register for raw input devices"), hWnd);
return FALSE;
}
return TRUE;
... in WndProc:
case WM_INPUT:
{
LONG lResult = Input(hWnd, lParam, ++ulCount);
if (lResult != 0) PostQuitMessage(lResult);
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
... and in the Input message handler:
LONG Input(HWND hWnd, LPARAM lParam, ULONG ulCount)
{
UINT dwSize = 0;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[dwSize];
if (lpb == NULL)
{
MessageBox(hWnd, TEXT("Unable to allocate buffer for raw input data!"), TEXT("Error"), MB_OK);
return 1;
}
std::unique_ptr<BYTE, void(*)(LPBYTE)> lpbd(lpb, [](LPBYTE p) { delete[] p; });
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
{
MessageBox(hWnd, TEXT("GetRawInputData returned incorrect size!"), TEXT("Error"), MB_OK);
return 1;
}
RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEKEYBOARD && raw->data.keyboard.VKey == 0x51)
{
OutputDebugString(TEXT("Q for Quit was pressed, exiting application\n"));
return 1;
}
TCHAR ridDeviceName[256];
dwSize = 256;
UINT dwResult = GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, &ridDeviceName, &dwSize);
if (dwResult == 0 || dwResult == UINT(-1))
{
return DisplayLastError(TEXT("Failed to get raw input device info"), hWnd);
}
const std::wstring devicePath(ridDeviceName);
OutputDebugString((std::to_wstring(ulCount) + L": Received WM_INPUT for USB device with path: " + devicePath + L"\n").c_str());
HKEY hKey;
std::wstring keypath = L"SYSTEM\\CurrentControlSet\\Enum\\USB";
LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keypath.c_str(), 0, KEY_READ, &hKey);
if (lResult != ERROR_SUCCESS)
{
keypath = keypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> hkeyd(hKey, [](HKEY h) { RegCloseKey(h); });
DWORD dwIndex = 0;
TCHAR subKeyName[256];
do
{
DWORD dwSubKeyNameLength = 256;
lResult = RegEnumKeyEx(hKey, dwIndex++, subKeyName, &dwSubKeyNameLength, NULL, NULL, NULL, NULL);
if (lResult != ERROR_SUCCESS && lResult != ERROR_NO_MORE_ITEMS)
{
keypath = keypath.insert(0, L"Failed to enumerate registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
if (lResult == ERROR_SUCCESS && devicePath.find(subKeyName) != -1)
{
const std::wstring hardwareId(subKeyName);
keypath += L"\\" + hardwareId;
HKEY hSubKey;
lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keypath.c_str(), 0, KEY_READ, &hSubKey);
if (lResult != ERROR_SUCCESS)
{
keypath = keypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> hsubkeyd(hSubKey, [](HKEY h) { RegCloseKey(h); });
// \\?\HID#VID_046D&PID_C016#7&d0f899c&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}
// vendorID productID ParentIdPrefix (without the &0000)
// \\?\HID#VID_413C&PID_2003#7&2a634b73&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}
// DeviceClass Guid, leads to prefixed info in registry
DWORD dwSubIndex = 0;
do
{
dwSubKeyNameLength = 256;
lResult = RegEnumKeyEx(hSubKey, dwSubIndex++, subKeyName, &dwSubKeyNameLength, NULL, NULL, NULL, NULL);
if (lResult != ERROR_SUCCESS && lResult != ERROR_NO_MORE_ITEMS)
{
keypath = keypath.insert(0, L"Failed to enumerate registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
if (lResult == ERROR_SUCCESS)
{
std::wstring targetkeypath = keypath + L"\\" + subKeyName;
HKEY hTargetKey;
lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, targetkeypath.c_str(), 0, KEY_READ, &hTargetKey);
if (lResult != ERROR_SUCCESS)
{
targetkeypath = targetkeypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> htargetkeyd(hTargetKey, [](HKEY h) { RegCloseKey(h); });
TCHAR valueBuffer[256];
DWORD dwBufferSize = 256;
lResult = RegQueryValueEx(hTargetKey, L"ParentIdPrefix", 0, NULL, (LPBYTE)valueBuffer, &dwBufferSize);
if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND)
{
targetkeypath = targetkeypath.insert(0, L"Failed to get registry value of ParentIdPrefix for key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}
if (lResult == ERROR_SUCCESS && devicePath.find(valueBuffer) != -1)
{
dwBufferSize = 256;
lResult = RegQueryValueEx(hTargetKey, L"LocationInformation", 0, NULL, (LPBYTE)valueBuffer, &dwBufferSize);
if (lResult != ERROR_SUCCESS)
{
targetkeypath = targetkeypath.insert(0, L"Failed to get registry value of LocationInformation for key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}
OutputDebugString((std::to_wstring(ulCount) + L": " + hardwareId + L" is located at: " + valueBuffer + L"\n").c_str());
}
}
}
while (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND);
}
}
while (lResult == ERROR_SUCCESS);
return ERROR_SUCCESS; // non-0 return codes indicate failure
}
UPDATE: I managed to get the location information using SetupDiGetDeviceRegistryProperty
but that just shows me the same location information that I got from the registry myself earlier. the USBview sample application must be rolling its own enumeration, once I find out what it's based on I'll use that as location information instead of the location information reported by the registry. I'd very much like to know why the USBview sample application reports reliable port numbering for USB connections (and based on what?) but location information maintained in the registry by the system seems to depend on connection order?
The USB port number a device is connected to is found in the registry in the Address value, provided the hub's driver set it correctly. Early Renesas USB3 drivers did not. You can check if the Address value is set correctly by means of my enhanced version of USBview, UsbTreeView. I don't know where exactly it comes from but for any USB device and USB hub CM_Get_DevInst_Registry_Property(CM_DRP_ADDRESS) or SetupDiGetDeviceRegistryProperty(SPDRP_ADDRESS) deliver the USB port number.
USBview uses a bottom to down approach using the USB API. Sinces you start with your device's DevicePath a bottom to up approach using the Setup API is more handy:
You need the DEVINST of your device to walk upwards the device tree by means of CM_Get_Parent and read the Address of each device until you hit the USB root hub or its host controller. Since the DevicePath is all you have you first need the device's InterfaceClassGuid. You get it by means of SetupDiOpenDeviceInterface.
With the InterfaceClassGuid you get a device list by means of SetupDiGetClassDevs. Request each device index by means of SetupDiEnumDeviceInterfaces (which provides the DevicePath) until you hit your device or it returns FALSE.
If you you hit your device you got the DevInst too and are arrived in the world of the CM_ API where you can walk upwards the device tree by means of CM_Get_Parent(&DevInstParent, DevInst). The first parent of your HID device is probably its USB device, whose parent is a USB standard hub or a USB root hub.
I've never seen a USB standard hub having a USB hardware serial number, therefore Windows creates a new device instance for it when it is attached to a new location. All you can get is its device instance id (CM_Get_Device_ID) with a fixed part like USB\VID_05E3&PID_0608 and a generated part at the end like 5&130B8FC2&0&3 for each new instance. If your device tree does not change then this should be good enough.
The USB port numbers are not arbitrary, they are fixed, so what UsbTreeView shows as "Port Chain" is fixed and gives an almost complete location information. As number for the host controller UsbTreeView uses its index in the GUID_DEVINTERFACE_USB_HOST_CONTROLLER SetupDi enumeration. This can change when a host controller is added or removed. So, instead of its enum index its device instance ID might be the better choice here.
USB port numbers are in the hardware, so they don't change.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With