From cb7bc2cf30f99177aae53caeddd5a94693f71baa Mon Sep 17 00:00:00 2001 From: Sam Martel Date: Sun, 1 Mar 2026 16:19:35 -0500 Subject: [PATCH 1/3] More work on usb enumeration --- .../details/are_there_string_descriptors.h | 65 ++++++ .../details/enumerate_hub_ports.h | 212 ++++++++++-------- .../details/get_bos_descriptor.h | 106 +++++++++ .../details/get_config_descriptor.h | 110 +++++++++ .../details/get_driver_key_name.h | 75 +++++++ .../details/usb_descriptor_request.h | 16 +- .../details/usb_interface_descriptor2.h | 37 +++ 7 files changed, 525 insertions(+), 96 deletions(-) create mode 100644 Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_bos_descriptor.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_config_descriptor.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h create mode 100644 Frasy/src/utils/usb_enumerator/details/usb_interface_descriptor2.h diff --git a/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h b/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h new file mode 100644 index 0000000..efcafc9 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h @@ -0,0 +1,65 @@ +/** + * @file are_there_string_descriptors.h + * @author Sam Martel + * @date 2026-03-01 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_ARE_THERE_STRING_DESCRIPTORS_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_ARE_THERE_STRING_DESCRIPTORS_H + +#include "oops.h" +#include "usb_interface_descriptor2.h" + +namespace Frasy::Usb::Details { +inline bool AreThereStringDescriptors(const USB_DEVICE_DESCRIPTOR* deviceDesc, + const USB_CONFIGURATION_DESCRIPTOR* configDesc) +{ + // Check device descriptor strings. + if (deviceDesc->iManufacturer != 0 || deviceDesc->iProduct != 0 || deviceDesc->iSerialNumber != 0) { return true; } + + // Check the configuration and interface descriptor strings. + const UCHAR* descEnd = reinterpret_cast(configDesc + configDesc->wTotalLength); + const auto* commonDesc = reinterpret_cast(configDesc); + while ((const PUCHAR)commonDesc + sizeof(USB_COMMON_DESCRIPTOR) < descEnd && + (const PUCHAR)commonDesc + commonDesc->bLength <= descEnd) { + switch (commonDesc->bDescriptorType) { + case USB_CONFIGURATION_DESCRIPTOR_TYPE: + case USB_OTHER_SPEED_CONFIGURATION_DESCRIPTOR_TYPE: + if (commonDesc->bLength != sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + break; + } + if (((PUSB_CONFIGURATION_DESCRIPTOR)commonDesc)->iConfiguration != 0) { return true; } + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + + case USB_INTERFACE_DESCRIPTOR_TYPE: + if (commonDesc->bLength != sizeof(USB_INTERFACE_DESCRIPTOR) && + commonDesc->bLength != sizeof(UsbInterfaceDescriptor2)) { + FRASY_USB_OOPS(); + break; + } + if (((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->iInterface != 0) { return true; } + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + default: commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); continue; + } + } + + return false; +} +} // namespace Frasy::Usb::Details + +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_ARE_THERE_STRING_DESCRIPTORS_H diff --git a/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h b/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h index ae50bbf..3fd5df3 100644 --- a/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h +++ b/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h @@ -20,12 +20,17 @@ #define FRASY_UTILS_USB_ENUMERATOR_DETAILS_ENUMERATE_HUB_PORTS_H #include "../usb_node.h" +#include "get_driver_key_name.h" +#include "usb_descriptor_request.h" +#include "get_config_descriptor.h" +#include "get_bos_descriptor.h" +#include "are_there_string_descriptors.h" #include "Brigerad/Core/Log.h" -#include -#include #include +#include +#include namespace Frasy::Usb::Details { inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) @@ -41,13 +46,13 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) // endpoint numbers 1-15 so there can be a maximum of 30 endpoints // per device configuration. static constexpr size_t pipeCount = 32; - static constexpr size_t conInfoExBuffSize = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + ( - sizeof(USB_PIPE_INFO) * pipeCount); + static constexpr size_t conInfoExBuffSize = + sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + (sizeof(USB_PIPE_INFO) * pipeCount); alignas(USB_NODE_CONNECTION_INFORMATION_EX) char conInfoExBuff[conInfoExBuffSize]; auto connectionInfoEx = reinterpret_cast(conInfoExBuff); USB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2 = { - .ConnectionIndex = port, - .Length = sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), + .ConnectionIndex = port, + .Length = sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), }; connectionInfoV2.SupportedUsbProtocols.Usb110 = 1; connectionInfoV2.SupportedUsbProtocols.Usb200 = 1; @@ -67,89 +72,110 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) &nBytes, nullptr); - // if (success == TRUE && nBytes == sizeof(USB_PORT_CONNECTOR_PROPERTIES)) { - // pcpBuff = std::unique_ptr( - // new(static_cast(alignof(USB_PORT_CONNECTOR_PROPERTIES))) char[pcpSizeGetter. - // ActualLength]); - // portConnectorProperties = reinterpret_cast(pcpBuff.get()); - // success = DeviceIoControl(device, - // IOCTL_USB_GET_PORT_CONNECTOR_PROPERTIES, - // portConnectorProperties, - // pcpSizeGetter.ActualLength, - // portConnectorProperties, - // pcpSizeGetter.ActualLength, - // &nBytes, - // nullptr); - // if (success != TRUE || nBytes != pcpSizeGetter.ActualLength) { - // BR_LOG_ERROR("UsbEnum", "Failed to get port connector properties for port {}", port); - // portConnectorProperties = nullptr; - // } - // } - // - // success = DeviceIoControl(device, - // IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, - // &connectionInfoV2, - // sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), - // &connectionInfoV2, - // sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), - // &nBytes, - // nullptr); - // if (success != TRUE || nBytes != sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2)) { - // BR_LOG_ERROR("UsbEnum", "Failed to get node connection information for port {}", port); - // connectionInfoV2 = {}; - // } - // - // connectionInfoEx->ConnectionIndex = port; - // success = DeviceIoControl(device, - // IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, - // connectionInfoEx, - // conInfoExBuffSize, - // connectionInfoEx, - // conInfoExBuffSize, - // &nBytesEx, - // nullptr); - // if (success == TRUE) { - // // Since the USB_NODE_CONNECTION_INFORMATION_EX is used to display - // // the device speed, but the hub driver doesn't support indication - // // of superspeed, we overwrite the value if the super speed - // // data structures are available and indicate the device is operating - // // at SuperSpeed. - // if (connectionInfoEx->Speed == UsbHighSpeed - // && (connectionInfoV2.Flags.DeviceIsOperatingAtSuperSpeedOrHigher || - // connectionInfoV2.Flags.DeviceIsOperatingAtSuperSpeedPlusOrHigher)) { - // connectionInfoEx->Speed = UsbSuperSpeed; - // } - // } - // else { - // // Try to use IOCTL_USB_GET_NODE_CONNECTION_INFORMATION instead of _EX - // alignas(USB_NODE_CONNECTION_INFORMATION_EX) char conInfoBuff[conInfoExBuffSize]; - // auto connectionInfo = reinterpret_cast(conInfoBuff); - // connectionInfo->ConnectionIndex = port; - // success = DeviceIoControl(device, - // IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, - // connectionInfo, - // conInfoExBuffSize, - // connectionInfo, - // conInfoExBuffSize, - // &nBytes, - // nullptr); - // if (success != TRUE) { - // FRASY_USB_OOPS(); - // return std::nullopt; - // } - // - // // Copy the information to the var we'll be using. - // connectionInfoEx->ConnectionIndex = connectionInfo->ConnectionIndex; - // connectionInfoEx->DeviceDescriptor = connectionInfo->DeviceDescriptor; - // connectionInfoEx->CurrentConfigurationValue = connectionInfo->CurrentConfigurationValue; - // connectionInfoEx->Speed = connectionInfo->LowSpeed == TRUE ? UsbLowSpeed : UsbFullSpeed; - // connectionInfoEx->DeviceIsHub = connectionInfo->DeviceIsHub; - // connectionInfoEx->DeviceAddress = connectionInfo->DeviceAddress; - // connectionInfoEx->NumberOfOpenPipes = connectionInfo->NumberOfOpenPipes; - // connectionInfoEx->ConnectionStatus = connectionInfo->ConnectionStatus; - // - // std::memcpy(connectionInfoEx->PipeList, connectionInfo->PipeList, sizeof(USB_PIPE_INFO) * pipeCount); - // } + if (success == TRUE && nBytes == sizeof(USB_PORT_CONNECTOR_PROPERTIES)) { + // TODO make this not UB lmao + pcpBuff = std::unique_ptr( + new /*(static_cast(alignof(USB_PORT_CONNECTOR_PROPERTIES)))*/ char[pcpSizeGetter + .ActualLength]); + portConnectorProperties = reinterpret_cast(pcpBuff.get()); + success = DeviceIoControl(device, + IOCTL_USB_GET_PORT_CONNECTOR_PROPERTIES, + portConnectorProperties, + pcpSizeGetter.ActualLength, + portConnectorProperties, + pcpSizeGetter.ActualLength, + &nBytes, + nullptr); + if (success != TRUE || nBytes != pcpSizeGetter.ActualLength) { + BR_LOG_ERROR("UsbEnum", "Failed to get port connector properties for port {}", port); + portConnectorProperties = nullptr; + } + } + + success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, + &connectionInfoV2, + sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), + &connectionInfoV2, + sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), + &nBytes, + nullptr); + if (success != TRUE || nBytes != sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2)) { + BR_LOG_ERROR("UsbEnum", "Failed to get node connection information for port {}", port); + connectionInfoV2 = {}; + } + + connectionInfoEx->ConnectionIndex = port; + success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, + connectionInfoEx, + conInfoExBuffSize, + connectionInfoEx, + conInfoExBuffSize, + &nBytesEx, + nullptr); + if (success == TRUE) { + // Since the USB_NODE_CONNECTION_INFORMATION_EX is used to display + // the device speed, but the hub driver doesn't support indication + // of superspeed, we overwrite the value if the super speed + // data structures are available and indicate the device is operating + // at SuperSpeed. + if (connectionInfoEx->Speed == UsbHighSpeed && + (connectionInfoV2.Flags.DeviceIsOperatingAtSuperSpeedOrHigher || + connectionInfoV2.Flags.DeviceIsOperatingAtSuperSpeedPlusOrHigher)) { + connectionInfoEx->Speed = UsbSuperSpeed; + } + } + else { + // Try to use IOCTL_USB_GET_NODE_CONNECTION_INFORMATION instead of _EX + alignas(USB_NODE_CONNECTION_INFORMATION_EX) char conInfoBuff[conInfoExBuffSize]; + auto connectionInfo = reinterpret_cast(conInfoBuff); + connectionInfo->ConnectionIndex = port; + success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, + connectionInfo, + conInfoExBuffSize, + connectionInfo, + conInfoExBuffSize, + &nBytes, + nullptr); + if (success != TRUE) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + // Copy the information to the var we'll be using. + connectionInfoEx->ConnectionIndex = connectionInfo->ConnectionIndex; + connectionInfoEx->DeviceDescriptor = connectionInfo->DeviceDescriptor; + connectionInfoEx->CurrentConfigurationValue = connectionInfo->CurrentConfigurationValue; + connectionInfoEx->Speed = connectionInfo->LowSpeed == TRUE ? UsbLowSpeed : UsbFullSpeed; + connectionInfoEx->DeviceIsHub = connectionInfo->DeviceIsHub; + connectionInfoEx->DeviceAddress = connectionInfo->DeviceAddress; + connectionInfoEx->NumberOfOpenPipes = connectionInfo->NumberOfOpenPipes; + connectionInfoEx->ConnectionStatus = connectionInfo->ConnectionStatus; + + std::memcpy(connectionInfoEx->PipeList, connectionInfo->PipeList, sizeof(USB_PIPE_INFO) * pipeCount); + } + + // If there is a device connected, get the Device Description. + DevicePnpStrings devProps; + if (connectionInfoEx->ConnectionStatus != NoDeviceConnected) { + auto maybeDriverKeyName = GetDriverKeyName(device, port); + if (maybeDriverKeyName.has_value()) { + auto maybeDeviceProps = DriverNameToDeviceProperties(maybeDriverKeyName.value()); + if (maybeDeviceProps.has_value()) { + devProps = std::move(maybeDeviceProps.value()); + } + } + } + + auto configDesc = GetConfigDescriptor(device, port, 0); + std::optional bosDesc; + if (configDesc.has_value() && connectionInfoEx->DeviceDescriptor.bcdUSB > 0x0200) { + bosDesc = GetBosDescriptor(device, port); + } + + if (configDesc.has_value() && AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor, configDesc->ConfigDesc())) {} return std::nullopt; } @@ -164,11 +190,13 @@ inline std::vector EnumerateHubPorts(HANDLE hub, uint8_t numPorts) for (uint8_t index = 1; index <= numPorts; index++) { auto maybeNode = EnumerateHubPort(hub, index); if (maybeNode.has_value()) { ports.push_back(maybeNode.value()); } - else { BR_LOG_ERROR("UsbEnum", "Failed to enumerate hub port {}", index); } + else { + BR_LOG_ERROR("UsbEnum", "Failed to enumerate hub port {}", index); + } } return ports; } -} +} // namespace Frasy::Usb::Details -#endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_ENUMERATE_HUB_PORTS_H +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_ENUMERATE_HUB_PORTS_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_bos_descriptor.h b/Frasy/src/utils/usb_enumerator/details/get_bos_descriptor.h new file mode 100644 index 0000000..8f12db3 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_bos_descriptor.h @@ -0,0 +1,106 @@ +/** + * @file get_bos_descriptor.h + * @author Sam Martel + * @date 2026-03-01 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_BOS_DESCRIPTOR_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_BOS_DESCRIPTOR_H + +#include "oops.h" +#include "usb_descriptor_request.h" + +#include +#include + +namespace Frasy::Usb::Details { +inline std::optional GetBosDescriptor(HANDLE handle, uint8_t index) +{ + uint8_t bosDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_BOS_DESCRIPTOR)]; + // Do some cursed things to avoid dynamically allocating stuff! :D + PUSB_DESCRIPTOR_REQUEST bosDescReq = reinterpret_cast(bosDescReqBuf); + PUSB_BOS_DESCRIPTOR bosDesc = + reinterpret_cast(bosDescReqBuf + sizeof(USB_DESCRIPTOR_REQUEST)); + + ULONG nBytes = sizeof(bosDescReqBuf); + // Zero-fill everything + memset(bosDescReqBuf, 0, nBytes); + + // Indicate the port from which the descriptor will be requested + bosDescReq->ConnectionIndex = index; + + // USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this + // IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request. + // + // USBD will automatically initialize these fields: + // bmRequest = 0x80 + // bRequest = 0x06 + // + // We must inititialize these fields: + // wValue = Descriptor Type (high) and Descriptor Index (low byte) + // wIndex = Zero (or Language ID for String Descriptors) + // wLength = Length of descriptor buffer + bosDescReq->SetupPacket.wValue = (USB_BOS_DESCRIPTOR_TYPE << 8); + bosDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + ULONG nBytesReturned = 0; + // Now issue the get descriptor request. + BOOL success = DeviceIoControl(handle, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + bosDescReq, + nBytes, + bosDescReq, + nBytes, + &nBytesReturned, + nullptr); + + // TODO Doesn't really make sense to check bosDesc when we have done nothing with it yet? + if (success == FALSE || nBytesReturned != nBytes || + bosDesc->wTotalLength < sizeof(USB_BOS_DESCRIPTOR)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + // Now request the entire Configuration Descriptor using a dynamically allocated buffer which is sized big enough to + // hold the entire descriptor. + nBytes = sizeof(USB_DESCRIPTOR_REQUEST) + bosDesc->wTotalLength; + std::unique_ptr bosDescBuf(new uint8_t[nBytes]); + bosDescReq = reinterpret_cast(bosDescBuf.get()); + bosDesc = reinterpret_cast(bosDescBuf.get() + sizeof(USB_DESCRIPTOR_REQUEST)); + + // Re-indicate the port from which the descriptor will be requested. + bosDescReq->ConnectionIndex = index; + bosDescReq->SetupPacket.wValue = (USB_BOS_DESCRIPTOR_TYPE << 8); + bosDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + success = DeviceIoControl(handle, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + bosDescReq, + nBytes, + bosDescReq, + nBytes, + &nBytesReturned, + nullptr); + if (success == FALSE || nBytesReturned != nBytes || + bosDesc->wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + return UsbDescriptorRequest {bosDescReq}; +} +} + +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_BOS_DESCRIPTOR_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_config_descriptor.h b/Frasy/src/utils/usb_enumerator/details/get_config_descriptor.h new file mode 100644 index 0000000..f6d7ff7 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_config_descriptor.h @@ -0,0 +1,110 @@ +/** + * @file get_config_descriptor.h + * @author Sam Martel + * @date 2026-03-01 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_CONFIG_DESCRIPTOR_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_CONFIG_DESCRIPTOR_H + +#include "oops.h" +#include "usb_descriptor_request.h" + +#include +#include + +#include + +namespace Frasy::Usb::Details { +inline std::optional GetConfigDescriptor(HANDLE handle, + uint8_t configIndex, + uint8_t descriptorIndex) +{ + uint8_t configDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_CONFIGURATION_DESCRIPTOR)]; + // Do some cursed things to avoid dynamically allocating stuff! :D + PUSB_DESCRIPTOR_REQUEST configDescReq = reinterpret_cast(configDescReqBuf); + PUSB_CONFIGURATION_DESCRIPTOR configDesc = + reinterpret_cast(configDescReqBuf + sizeof(USB_DESCRIPTOR_REQUEST)); + + ULONG nBytes = sizeof(configDescReqBuf); + // Zero-fill everything + memset(configDescReqBuf, 0, nBytes); + + // Indicate the port from which the descriptor will be requested + configDescReq->ConnectionIndex = configIndex; + + // USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this + // IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request. + // + // USBD will automatically initialize these fields: + // bmRequest = 0x80 + // bRequest = 0x06 + // + // We must inititialize these fields: + // wValue = Descriptor Type (high) and Descriptor Index (low byte) + // wIndex = Zero (or Language ID for String Descriptors) + // wLength = Length of descriptor buffer + configDescReq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | descriptorIndex; + configDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + ULONG nBytesReturned = 0; + // Now issue the get descriptor request. + BOOL success = DeviceIoControl(handle, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + configDescReq, + nBytes, + configDescReq, + nBytes, + &nBytesReturned, + nullptr); + + // TODO Doesn't really make sense to check configDesc when we have done nothing with it yet? + if (success == FALSE || nBytesReturned != nBytes || + configDesc->wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + // Now request the entire Configuration Descriptor using a dynamically allocated buffer which is sized big enough to + // hold the entire descriptor. + nBytes = sizeof(USB_DESCRIPTOR_REQUEST) + configDesc->wTotalLength; + std::unique_ptr configDescBuf(new uint8_t[nBytes]); + configDescReq = reinterpret_cast(configDescBuf.get()); + configDesc = reinterpret_cast(configDescBuf.get() + sizeof(USB_DESCRIPTOR_REQUEST)); + + // Re-indicate the port from which the descriptor will be requested. + configDescReq->ConnectionIndex = configIndex; + configDescReq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | descriptorIndex; + configDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + success = DeviceIoControl(handle, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + configDescReq, + nBytes, + configDescReq, + nBytes, + &nBytesReturned, + nullptr); + if (success == FALSE || nBytesReturned != nBytes || + configDesc->wTotalLength < sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + return UsbDescriptorRequest {configDescReq}; +} +} // namespace Frasy::Usb::Details + +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_CONFIG_DESCRIPTOR_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h b/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h new file mode 100644 index 0000000..79581dd --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h @@ -0,0 +1,75 @@ +/** + * @file get_driver_key_name.h + * @author Sam Martel + * @date 2026-03-01 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_GET_DRIVER_KEY_NAME_H +#define FRASY_UTILS_USB_ENUMERATOR_GET_DRIVER_KEY_NAME_H + +#include "oops.h" + +#include "Brigerad/Utils/types/wstring_to_utf8.h" + +#include +#include + +namespace Frasy::Usb { +inline std::optional GetDriverKeyName(HANDLE device, uint8_t index) +{ + USB_NODE_CONNECTION_DRIVERKEY_NAME driverKeyName; + ULONG nBytes = 0; + // Get the length of the name of the driver key of the device attached to the specified port. + driverKeyName.ConnectionIndex = index; + BOOL success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, + &driverKeyName, + sizeof(driverKeyName), + &driverKeyName, + sizeof(driverKeyName), + &nBytes, + nullptr); + if (!success) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + nBytes = driverKeyName.ActualLength; + if (nBytes <= sizeof(driverKeyName)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + PUSB_NODE_CONNECTION_DRIVERKEY_NAME driverKeyNameW = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)malloc(nBytes); + driverKeyNameW->ActualLength = nBytes; + success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, + driverKeyNameW, + nBytes, + driverKeyNameW, + nBytes, + &nBytes, + nullptr); + std::wstring wname = std::wstring(driverKeyNameW->DriverKeyName); + free(driverKeyNameW); + if (!success) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + return wstring_to_utf8(wname); +} +} // namespace Frasy::Usb + +#endif // FRASY_UTILS_USB_ENUMERATOR_GET_DRIVER_KEY_NAME_H diff --git a/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h b/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h index f99394b..37d065e 100644 --- a/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h +++ b/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h @@ -18,8 +18,10 @@ #ifndef STRATO_USB_DESCRIPTOR_REQUEST_H #define STRATO_USB_DESCRIPTOR_REQUEST_H -#include +#include + #include +#include #include @@ -44,6 +46,12 @@ struct UsbDescriptorRequest { Data.assign(pO->Data, pO->Data + pO->SetupPacket.wLength); } + PUSB_CONFIGURATION_DESCRIPTOR ConfigDesc() + { + BR_ASSERT(Data.size() < sizeof(USB_CONFIGURATION_DESCRIPTOR), "Not a config descriptor!"); + return reinterpret_cast(Data.data()); + } + uint32_t ConnectionIndex = 0; struct { @@ -54,7 +62,7 @@ struct UsbDescriptorRequest { uint16_t wLength = 0; } SetupPacket; - std::string Data; + std::vector Data; }; -} -#endif //STRATO_USB_DESCRIPTOR_REQUEST_H +} // namespace Frasy::Usb +#endif // STRATO_USB_DESCRIPTOR_REQUEST_H diff --git a/Frasy/src/utils/usb_enumerator/details/usb_interface_descriptor2.h b/Frasy/src/utils/usb_enumerator/details/usb_interface_descriptor2.h new file mode 100644 index 0000000..90c3712 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/usb_interface_descriptor2.h @@ -0,0 +1,37 @@ +/** + * @file usb_interface_descriptor2.h + * @author Sam Martel + * @date 2026-03-01 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_USB_INTERFACE_DESCRIPTOR2_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_USB_INTERFACE_DESCRIPTOR2_H + +namespace Frasy::Usb::Details { +struct UsbInterfaceDescriptor2 { + UCHAR bLength = {}; // offset 0, size 1 + UCHAR bDescriptorType = {}; // offset 1, size 1 + UCHAR bInterfaceNumber = {}; // offset 2, size 1 + UCHAR bAlternateSetting = {}; // offset 3, size 1 + UCHAR bNumEndpoints = {}; // offset 4, size 1 + UCHAR bInterfaceClass = {}; // offset 5, size 1 + UCHAR bInterfaceSubClass = {}; // offset 6, size 1 + UCHAR bInterfaceProtocol = {}; // offset 7, size 1 + UCHAR iInterface = {}; // offset 8, size 1 + USHORT wNumClasses = {}; // offset 9, size 2 +}; +} // namespace Frasy::Usb::Utils + +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_USB_INTERFACE_DESCRIPTOR2_H From 27cd87aaf8e24c9d424eec6d99990b40fce6e410 Mon Sep 17 00:00:00 2001 From: Sam Martel Date: Tue, 3 Mar 2026 10:51:31 -0500 Subject: [PATCH 2/3] UsbEnum complete, still missing com port finding... --- .../details/are_there_string_descriptors.h | 2 + .../details/driver_name_to_device_inst.h | 161 +++++++++--------- .../driver_name_to_device_properties.h | 36 ++-- .../usb_enumerator/details/enumerate_hub.h | 25 ++- .../details/enumerate_hub_ports.h | 76 +++++++-- .../details/get_all_string_descriptors.h | 121 +++++++++++++ .../details/get_device_property.h | 2 + .../details/get_driver_key_name.h | 1 + .../details/get_external_hub_name.h | 70 ++++++++ .../details/get_string_descriptor.h | 105 ++++++++++++ .../details/get_string_descriptors.h | 46 +++++ .../details/usb_descriptor_request.h | 2 +- .../utils/usb_enumerator/external_hub_info.h | 23 +-- Frasy/src/utils/usb_enumerator/pnp_strings.h | 2 + .../src/utils/usb_enumerator/root_hub_info.h | 1 + .../utils/usb_enumerator/string_descriptor.h | 43 ++++- .../utils/usb_enumerator/usb_device_info.h | 16 +- .../utils/usb_enumerator/usb_enumerator.cpp | 44 ++--- 18 files changed, 606 insertions(+), 170 deletions(-) create mode 100644 Frasy/src/utils/usb_enumerator/details/get_all_string_descriptors.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_external_hub_name.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_string_descriptor.h create mode 100644 Frasy/src/utils/usb_enumerator/details/get_string_descriptors.h diff --git a/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h b/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h index efcafc9..28517e0 100644 --- a/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h +++ b/Frasy/src/utils/usb_enumerator/details/are_there_string_descriptors.h @@ -22,6 +22,8 @@ #include "oops.h" #include "usb_interface_descriptor2.h" +#include + namespace Frasy::Usb::Details { inline bool AreThereStringDescriptors(const USB_DEVICE_DESCRIPTOR* deviceDesc, const USB_CONFIGURATION_DESCRIPTOR* configDesc) diff --git a/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_inst.h b/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_inst.h index 6f3092d..a29d6fa 100644 --- a/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_inst.h +++ b/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_inst.h @@ -18,6 +18,7 @@ #ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_DRIVER_NAME_TO_DEVICE_INST_H #define FRASY_UTILS_USB_ENUMERATOR_DETAILS_DRIVER_NAME_TO_DEVICE_INST_H +#include "get_device_property.h" namespace Frasy::Usb::Details { struct DeviceProps { @@ -37,95 +38,97 @@ inline std::optional DriverNameToDeviceInst(const std::string& driv // would force us to retry. Instead, we use Setup API to snapshot all // devices. // TODO have a way to refresh the cache. - // struct DeviceCacheEntry { - // HDEVINFO deviceInfo; - // SP_DEVINFO_DATA deviceInfoData; - // std::string driverName; - // }; - // static std::vector deviceCache; - // if (gRefreshCache) { - // BR_LOG_DEBUG("UsbEnum", "Refreshing device cache"); - // gRefreshCache = false; - // deviceCache.clear(); - // - // // Examine all present devices to see if any match the given DriverName - // HDEVINFO deviceInfo = SetupDiGetClassDevsA(nullptr, nullptr, nullptr, DIGCF_ALLCLASSES | DIGCF_PRESENT); - // if (deviceInfo == INVALID_HANDLE_VALUE) { - // FRASY_USB_OOPS(); - // return std::nullopt; - // } - // - // ULONG deviceIndex = 0; - // SP_DEVINFO_DATA deviceInfoData; - // memset(&deviceInfoData, 0, sizeof(SP_DEVINFO_DATA)); - // deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); - // - // BOOL done = FALSE; - // while (done == FALSE) { - // // Get devinst of the next device. - // BOOL status = SetupDiEnumDeviceInfo(deviceInfo, deviceIndex, &deviceInfoData); - // deviceIndex++; - // if (status == FALSE) { - // // Could be an error, or indication that all devices have been processed. Either way, the desired device was not found. - // if (GetLastError() != ERROR_NO_MORE_ITEMS) { FRASY_USB_OOPS(); } - // done = TRUE; - // break; - // } - // - // // Get the DriverName value - // auto maybeName = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_DRIVER); - // if (maybeName.has_value()) { - // deviceCache.emplace_back(deviceInfo, deviceInfoData, maybeName.value()); - // } - // } - // std::ranges::sort(deviceCache, [](const auto& a, const auto& b) { return a.driverName < b.driverName; }); - // } - // - // auto it = std::ranges::find_if(deviceCache, - // [&driverName](const DeviceCacheEntry& dce) { return dce.driverName == driverName; }); - // if (it != deviceCache.end()) { - // return DeviceProps{ - // .deviceInfo = it->deviceInfo, - // .deviceInfoData = it->deviceInfoData}; - // } + struct DeviceCacheEntry { + HDEVINFO deviceInfo; + SP_DEVINFO_DATA deviceInfoData; + std::string driverName; + }; + static std::vector deviceCache; + if (gRefreshCache) { + BR_LOG_DEBUG("UsbEnum", "Refreshing device cache"); + gRefreshCache = false; + deviceCache.clear(); + // Examine all present devices to see if any match the given DriverName + HDEVINFO deviceInfo = SetupDiGetClassDevsA(nullptr, nullptr, nullptr, DIGCF_ALLCLASSES | DIGCF_PRESENT); + if (deviceInfo == INVALID_HANDLE_VALUE) { + FRASY_USB_OOPS(); + return std::nullopt; + } - // Examine all present devices to see if any match the given DriverName - HDEVINFO deviceInfo = SetupDiGetClassDevsA(nullptr, nullptr, nullptr, DIGCF_ALLCLASSES | DIGCF_PRESENT); - if (deviceInfo == INVALID_HANDLE_VALUE) { - FRASY_USB_OOPS(); - return std::nullopt; - } + ULONG deviceIndex = 0; + SP_DEVINFO_DATA deviceInfoData; + memset(&deviceInfoData, 0, sizeof(SP_DEVINFO_DATA)); + deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); - ULONG deviceIndex = 0; - SP_DEVINFO_DATA deviceInfoData; - memset(&deviceInfoData, 0, sizeof(SP_DEVINFO_DATA)); - deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + BOOL done = FALSE; + while (done == FALSE) { + // Get devinst of the next device. + BOOL status = SetupDiEnumDeviceInfo(deviceInfo, deviceIndex, &deviceInfoData); + deviceIndex++; + if (status == FALSE) { + // Could be an error, or indication that all devices have been processed. Either way, the desired device was not found. + if (GetLastError() != ERROR_NO_MORE_ITEMS) { FRASY_USB_OOPS(); } + done = TRUE; + break; + } - BOOL done = FALSE; - while (done == FALSE) { - // Get devinst of the next device. - BOOL status = SetupDiEnumDeviceInfo(deviceInfo, deviceIndex, &deviceInfoData); - deviceIndex++; - if (status == FALSE) { - // Could be an error, or indication that all devices have been processed. Either way, the desired device was not found. - if (GetLastError() != ERROR_NO_MORE_ITEMS) { FRASY_USB_OOPS(); } - done = TRUE; - break; - } - - // Get the DriverName value - auto maybeName = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_DRIVER); - if (maybeName.has_value() && maybeName.value() == driverName) { - return DeviceProps{deviceInfo, deviceInfoData}; + // Get the DriverName value + auto maybeName = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_DRIVER); + if (maybeName.has_value()) { + deviceCache.emplace_back(deviceInfo, deviceInfoData, maybeName.value()); + } } + std::ranges::sort(deviceCache, [](const auto& a, const auto& b) { return a.driverName < b.driverName; }); } - if (deviceInfo != INVALID_HANDLE_VALUE) { - SetupDiDestroyDeviceInfoList(deviceInfo); + auto it = std::ranges::find_if(deviceCache, + [&driverName](const DeviceCacheEntry& dce) { return dce.driverName == driverName; }); + if (it != deviceCache.end()) { + return DeviceProps{ + .deviceInfo = it->deviceInfo, + .deviceInfoData = it->deviceInfoData}; } BR_LOG_ERROR("UsbEnum", "Could not find device with driver name: {}", driverName); return std::nullopt; + + + // Examine all present devices to see if any match the given DriverName + // HDEVINFO deviceInfo = SetupDiGetClassDevsA(nullptr, nullptr, nullptr, DIGCF_ALLCLASSES | DIGCF_PRESENT); + // if (deviceInfo == INVALID_HANDLE_VALUE) { + // FRASY_USB_OOPS(); + // return std::nullopt; + // } + // + // ULONG deviceIndex = 0; + // SP_DEVINFO_DATA deviceInfoData; + // memset(&deviceInfoData, 0, sizeof(SP_DEVINFO_DATA)); + // deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + // + // BOOL done = FALSE; + // while (done == FALSE) { + // // Get devinst of the next device. + // BOOL status = SetupDiEnumDeviceInfo(deviceInfo, deviceIndex, &deviceInfoData); + // deviceIndex++; + // if (status == FALSE) { + // // Could be an error, or indication that all devices have been processed. Either way, the desired device was not found. + // if (GetLastError() != ERROR_NO_MORE_ITEMS) { FRASY_USB_OOPS(); } + // done = TRUE; + // break; + // } + // + // // Get the DriverName value + // auto maybeName = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_DRIVER); + // if (maybeName.has_value() && maybeName.value() == driverName) { + // return DeviceProps{deviceInfo, deviceInfoData}; + // } + // } + // + // if (deviceInfo != INVALID_HANDLE_VALUE) { + // SetupDiDestroyDeviceInfoList(deviceInfo); + // } + // BR_LOG_ERROR("UsbEnum", "Could not find device with driver name: {}", driverName); + // return std::nullopt; } } #endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_DRIVER_NAME_TO_DEVICE_INST_H diff --git a/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_properties.h b/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_properties.h index 9a099ec..6c6fbdd 100644 --- a/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_properties.h +++ b/Frasy/src/utils/usb_enumerator/details/driver_name_to_device_properties.h @@ -20,8 +20,8 @@ #define FRASY_UTILS_USB_ENUMERATOR_DRIVER_NAME_TO_DEVICE_PROPERTIES_H #include "../pnp_strings.h" -#include "get_device_property.h" #include "driver_name_to_device_inst.h" +#include "get_device_property.h" #include @@ -31,14 +31,12 @@ namespace Frasy::Usb::Details { inline std::optional DriverNameToDeviceProperties(const std::string& driverName) { auto maybeInstance = DriverNameToDeviceInst(driverName); - if (!maybeInstance) { - return std::nullopt; - } + if (!maybeInstance) { return std::nullopt; } auto& [deviceInfo, deviceInfoData] = maybeInstance.value(); ULONG requiredLen = 0; - BOOL status = SetupDiGetDeviceInstanceIdA(deviceInfo, &deviceInfoData, nullptr, 0, &requiredLen); - LONG lastError = GetLastError(); + BOOL status = SetupDiGetDeviceInstanceIdA(deviceInfo, &deviceInfoData, nullptr, 0, &requiredLen); + LONG lastError = GetLastError(); if (status != FALSE && lastError != ERROR_INSUFFICIENT_BUFFER) { FRASY_USB_OOPS(); return std::nullopt; @@ -48,36 +46,32 @@ inline std::optional DriverNameToDeviceProperties(const std::s requiredLen++; DevicePnpStrings devicePnpStrings; devicePnpStrings.deviceId = std::string(requiredLen, '\0'); - status = SetupDiGetDeviceInstanceIdA(deviceInfo, &deviceInfoData, (PSTR)devicePnpStrings.deviceId.data(), requiredLen, &requiredLen); + status = SetupDiGetDeviceInstanceIdA( + deviceInfo, &deviceInfoData, (PSTR)devicePnpStrings.deviceId.data(), requiredLen, &requiredLen); if (status == FALSE) { FRASY_USB_OOPS(); return std::nullopt; } auto maybeDesc = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_DEVICEDESC); - if (maybeDesc.has_value()) { - devicePnpStrings.deviceDesc = maybeDesc.value(); - } else { + if (maybeDesc.has_value()) { devicePnpStrings.deviceDesc = maybeDesc.value(); } + else { FRASY_USB_OOPS(); return std::nullopt; } // We don't fail if the following registry query fails as these fields are additional information only auto maybeProp = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_HARDWAREID); - if (maybeProp.has_value()) { - devicePnpStrings.hwId = maybeProp.value(); - } + if (maybeProp.has_value()) { devicePnpStrings.hwId = maybeProp.value(); } maybeProp = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_SERVICE); - if (maybeProp.has_value()) { - devicePnpStrings.service = maybeProp.value(); - } + if (maybeProp.has_value()) { devicePnpStrings.service = maybeProp.value(); } maybeProp = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_CLASS); - if (maybeProp.has_value()) { - devicePnpStrings.deviceClass = maybeProp.value(); - } + if (maybeProp.has_value()) { devicePnpStrings.deviceClass = maybeProp.value(); } + maybeProp = GetDeviceProperty(deviceInfo, &deviceInfoData, SPDRP_FRIENDLYNAME); + if (maybeProp.has_value()) { devicePnpStrings.friendlyName = maybeProp.value(); } return devicePnpStrings; } -} +} // namespace Frasy::Usb::Details -#endif //FRASY_UTILS_USB_ENUMERATOR_DRIVER_NAME_TO_DEVICE_PROPERTIES_H +#endif // FRASY_UTILS_USB_ENUMERATOR_DRIVER_NAME_TO_DEVICE_PROPERTIES_H diff --git a/Frasy/src/utils/usb_enumerator/details/enumerate_hub.h b/Frasy/src/utils/usb_enumerator/details/enumerate_hub.h index 3ac71fa..6544620 100644 --- a/Frasy/src/utils/usb_enumerator/details/enumerate_hub.h +++ b/Frasy/src/utils/usb_enumerator/details/enumerate_hub.h @@ -35,14 +35,14 @@ namespace Frasy::Usb::Details { inline std::variant EnumerateHub( - const std::string& hubName, - PUSB_NODE_CONNECTION_INFORMATION_EX connectionInfo = nullptr, - PUSB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2 = nullptr, - PUSB_PORT_CONNECTOR_PROPERTIES portConnectorProperties = nullptr, - PUSB_DESCRIPTOR_REQUEST configDesc = nullptr, - PUSB_DESCRIPTOR_REQUEST bosDesc = nullptr, - const std::vector& stringDescriptors = {}, - std::optional devicePnpStrings = std::nullopt) + const std::string& hubName, + PUSB_NODE_CONNECTION_INFORMATION_EX connectionInfo = nullptr, + PUSB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2 = nullptr, + PUSB_PORT_CONNECTOR_PROPERTIES portConnectorProperties = nullptr, + std::optional configDesc = std::nullopt, + std::optional bosDesc = std::nullopt, + const std::vector& stringDescriptors = {}, + std::optional devicePnpStrings = std::nullopt) { std::variant devInfo; USB_NODE_INFORMATION hubInfo; @@ -55,12 +55,12 @@ inline std::variant EnumerateHub( [&](ExternalHubInfo& info) { info.hubName = hubName; info.connectionInfo = connectionInfo; - info.configDesc = configDesc; + info.configDesc = configDesc.value_or(UsbDescriptorRequest{}); info.stringDescriptors = stringDescriptors; info.portConnectorProps = portConnectorProperties; info.hubInfoEx = hubInfoEx; info.hubCapabilitiesEx = hubCapabilitiesEx; - info.bosDesc = bosDesc; + info.bosDesc = bosDesc.value_or(UsbDescriptorRequest{}); info.connectionInfoV2 = *connectionInfoV2; if (devicePnpStrings.has_value()) { info.setDevicePnpStrings(*devicePnpStrings); } }, @@ -148,8 +148,7 @@ inline std::variant EnumerateHub( [&](ExternalHubInfo&) { leafName += hubName; }); } - // TODO is this where this goes? lol - Frasy::visit(devInfo, [&](auto&& info) { info.deviceDescName = leafName; }); + Frasy::visit(devInfo, [&](auto&& info) { info.leafName = leafName; }); // Recursively list the ports of this hub. Frasy::visit(devInfo, @@ -161,5 +160,5 @@ inline std::variant EnumerateHub( return devInfo; } -} // namespace Frasy::Usb::Details +} // namespace Frasy::Usb::Details #endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_ENUMERATE_HUB_H diff --git a/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h b/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h index 3fd5df3..fda98dc 100644 --- a/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h +++ b/Frasy/src/utils/usb_enumerator/details/enumerate_hub_ports.h @@ -25,6 +25,9 @@ #include "get_config_descriptor.h" #include "get_bos_descriptor.h" #include "are_there_string_descriptors.h" +#include "get_all_string_descriptors.h" +#include "get_external_hub_name.h" +#include "format/format.h" #include "Brigerad/Core/Log.h" @@ -33,6 +36,16 @@ #include namespace Frasy::Usb::Details { +std::variant EnumerateHub( + const std::string& hubName, + PUSB_NODE_CONNECTION_INFORMATION_EX connectionInfo, + PUSB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2, + PUSB_PORT_CONNECTOR_PROPERTIES portConnectorProperties, + std::optional configDesc, + std::optional bosDesc, + const std::vector& stringDescriptors, + std::optional devicePnpStrings); + inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) { ULONG nBytesEx = 0; @@ -45,14 +58,14 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) // Descriptor. There can be an IN endpoint and an OUT endpoint at // endpoint numbers 1-15 so there can be a maximum of 30 endpoints // per device configuration. - static constexpr size_t pipeCount = 32; + static constexpr size_t pipeCount = 32; static constexpr size_t conInfoExBuffSize = - sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + (sizeof(USB_PIPE_INFO) * pipeCount); + sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + (sizeof(USB_PIPE_INFO) * pipeCount); alignas(USB_NODE_CONNECTION_INFORMATION_EX) char conInfoExBuff[conInfoExBuffSize]; auto connectionInfoEx = reinterpret_cast(conInfoExBuff); USB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2 = { - .ConnectionIndex = port, - .Length = sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), + .ConnectionIndex = port, + .Length = sizeof(USB_NODE_CONNECTION_INFORMATION_EX_V2), }; connectionInfoV2.SupportedUsbProtocols.Usb110 = 1; connectionInfoV2.SupportedUsbProtocols.Usb200 = 1; @@ -75,9 +88,10 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) if (success == TRUE && nBytes == sizeof(USB_PORT_CONNECTOR_PROPERTIES)) { // TODO make this not UB lmao pcpBuff = std::unique_ptr( - new /*(static_cast(alignof(USB_PORT_CONNECTOR_PROPERTIES)))*/ char[pcpSizeGetter - .ActualLength]); + new /*(static_cast(alignof(USB_PORT_CONNECTOR_PROPERTIES)))*/ char[pcpSizeGetter + .ActualLength]); portConnectorProperties = reinterpret_cast(pcpBuff.get()); + portConnectorProperties->ConnectionIndex = port; success = DeviceIoControl(device, IOCTL_USB_GET_PORT_CONNECTOR_PROPERTIES, portConnectorProperties, @@ -129,9 +143,9 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) else { // Try to use IOCTL_USB_GET_NODE_CONNECTION_INFORMATION instead of _EX alignas(USB_NODE_CONNECTION_INFORMATION_EX) char conInfoBuff[conInfoExBuffSize]; - auto connectionInfo = reinterpret_cast(conInfoBuff); + auto connectionInfo = reinterpret_cast(conInfoBuff); connectionInfo->ConnectionIndex = port; - success = DeviceIoControl(device, + success = DeviceIoControl(device, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, connectionInfo, conInfoExBuffSize, @@ -159,8 +173,9 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) // If there is a device connected, get the Device Description. DevicePnpStrings devProps; + std::optional maybeDriverKeyName; if (connectionInfoEx->ConnectionStatus != NoDeviceConnected) { - auto maybeDriverKeyName = GetDriverKeyName(device, port); + maybeDriverKeyName = GetDriverKeyName(device, port); if (maybeDriverKeyName.has_value()) { auto maybeDeviceProps = DriverNameToDeviceProperties(maybeDriverKeyName.value()); if (maybeDeviceProps.has_value()) { @@ -169,15 +184,50 @@ inline std::optional EnumerateHubPort(HANDLE device, uint8_t port) } } - auto configDesc = GetConfigDescriptor(device, port, 0); + auto configDesc = GetConfigDescriptor(device, port, 0); std::optional bosDesc; if (configDesc.has_value() && connectionInfoEx->DeviceDescriptor.bcdUSB > 0x0200) { bosDesc = GetBosDescriptor(device, port); } - if (configDesc.has_value() && AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor, configDesc->ConfigDesc())) {} + std::optional> stringDescriptors; + if (configDesc.has_value() && AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor, + configDesc->ConfigDesc())) { + stringDescriptors = GetAllStringDescriptors(device, + port, + &connectionInfoEx->DeviceDescriptor, + configDesc->ConfigDesc()); + } - return std::nullopt; + // If the device connected to the port is an external hub, get the name of the external hub and recursively enumerate it. + if (connectionInfoEx->DeviceIsHub) { + std::optional hubName = GetExternalHubName(device, port); + if (hubName.has_value()) { + BR_LOG_TRACE("UsbEnum", "Enumerating External hub: {}", hubName.value()); + return std::get<1>(EnumerateHub(hubName.value(), + connectionInfoEx, + &connectionInfoV2, + portConnectorProperties, + configDesc, + bosDesc, + stringDescriptors.value_or({}), + devProps)); + } + } + else { + auto devInfo = DeviceInfo{ + .leafName = std::format("[Port{}] {}: {}", port, connectionInfoEx->ConnectionStatus, devProps.deviceDesc), + .connectionInfo = connectionInfoEx, + .portConnectorProps = portConnectorProperties, + .configDesc = configDesc.value_or({}), + .bosDesc = bosDesc.value_or({}), + .stringDescriptors = stringDescriptors.value_or({}), + .connectionInfoV2 = connectionInfoV2, + }; + devInfo.setDevicePnpStrings(devProps); + + return devInfo; + } } inline std::vector EnumerateHubPorts(HANDLE hub, uint8_t numPorts) @@ -197,6 +247,6 @@ inline std::vector EnumerateHubPorts(HANDLE hub, uint8_t numPorts) return ports; } -} // namespace Frasy::Usb::Details +} // namespace Frasy::Usb::Details #endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_ENUMERATE_HUB_PORTS_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_all_string_descriptors.h b/Frasy/src/utils/usb_enumerator/details/get_all_string_descriptors.h new file mode 100644 index 0000000..3d60089 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_all_string_descriptors.h @@ -0,0 +1,121 @@ +/** + * @file get_all_string_descriptors.h + * @author Sam Martel + * @date 2026-03-02 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_ALL_STRING_DESCRIPTORS_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_ALL_STRING_DESCRIPTORS_H + +#include "get_string_descriptor.h" +#include "get_string_descriptors.h" +#include "string_descriptor.h" + +#include +#include +#include + +namespace Frasy::Usb::Details { +inline std::optional> GetAllStringDescriptors( + HANDLE device, uint8_t port, const USB_DEVICE_DESCRIPTOR* devDesc, const USB_CONFIGURATION_DESCRIPTOR* confDesc) +{ + auto stringDescriptors = std::vector(1); // index 0 is supported languages. + + // Get the array of supported language IDs, which is returned in String Descriptor 0. + auto maybeLanguages = GetStringDescriptor(device, port, 0, 0); + if (!maybeLanguages.has_value()) { return std::nullopt; } + stringDescriptors[0] = maybeLanguages.value(); + + // Get them strings. + if (devDesc->iManufacturer != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, port, devDesc->iManufacturer, stringDescriptors[0].stringDescriptor.string)); + } + if (devDesc->iProduct != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, port, devDesc->iProduct, stringDescriptors[0].stringDescriptor.string)); + } + if (devDesc->iSerialNumber != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, port, devDesc->iSerialNumber, stringDescriptors[0].stringDescriptor.string)); + } + + // Get the configuration and interface descriptor strings. + const uint8_t* descEnd = reinterpret_cast(confDesc + confDesc->wTotalLength); + const auto* commonDesc = reinterpret_cast(confDesc); + + while ((const uint8_t*)commonDesc + sizeof(USB_COMMON_DESCRIPTOR) < descEnd && + (const uint8_t*)commonDesc + commonDesc->bLength <= descEnd) { + if (commonDesc->bLength == 0) { break; } + switch (commonDesc->bDescriptorType) { + // case USB_ENDPOINT_DESCRIPTOR_TYPE: break; // TODO might want to implement that. + case USB_CONFIGURATION_DESCRIPTOR_TYPE: { + if (commonDesc->bLength != sizeof(USB_CONFIGURATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + break; + } + if (((PUSB_CONFIGURATION_DESCRIPTOR)commonDesc)->iConfiguration != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, + port, + ((PUSB_CONFIGURATION_DESCRIPTOR)commonDesc)->iConfiguration, + stringDescriptors[0].stringDescriptor.string)); + } + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + } + case USB_INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE: { + if (commonDesc->bLength < sizeof(USB_INTERFACE_ASSOCIATION_DESCRIPTOR)) { + FRASY_USB_OOPS(); + break; + } + if (((PUSB_INTERFACE_ASSOCIATION_DESCRIPTOR)commonDesc)->iFunction != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, + port, + ((PUSB_INTERFACE_ASSOCIATION_DESCRIPTOR)commonDesc)->iFunction, + stringDescriptors[0].stringDescriptor.string)); + } + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + } + case USB_INTERFACE_DESCRIPTOR_TYPE: { + if (commonDesc->bLength != sizeof(USB_INTERFACE_DESCRIPTOR) && + commonDesc->bLength != sizeof(UsbInterfaceDescriptor2)) { + FRASY_USB_OOPS(); + break; + } + if (((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->iInterface != 0) { + stringDescriptors.append_range( + GetStringDescriptors(device, + port, + ((PUSB_INTERFACE_DESCRIPTOR)commonDesc)->iInterface, + stringDescriptors[0].stringDescriptor.string)); + } + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + } + default: + BR_LOG_DEBUG("UsbEnum", "Skipping unknown descriptor type: {:02x}", commonDesc->bDescriptorType); + commonDesc = (PUSB_COMMON_DESCRIPTOR)((PUCHAR)commonDesc + commonDesc->bLength); + continue; + } + } + + return stringDescriptors; +} +} // namespace Frasy::Usb::Details + +#endif // FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_ALL_STRING_DESCRIPTORS_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_device_property.h b/Frasy/src/utils/usb_enumerator/details/get_device_property.h index 28adbcd..e92e1c5 100644 --- a/Frasy/src/utils/usb_enumerator/details/get_device_property.h +++ b/Frasy/src/utils/usb_enumerator/details/get_device_property.h @@ -24,6 +24,8 @@ #include #include +#include + namespace Frasy::Usb::Details { inline std::optional GetDeviceProperty(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData, diff --git a/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h b/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h index 79581dd..4e5ec69 100644 --- a/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h +++ b/Frasy/src/utils/usb_enumerator/details/get_driver_key_name.h @@ -53,6 +53,7 @@ inline std::optional GetDriverKeyName(HANDLE device, uint8_t index) } PUSB_NODE_CONNECTION_DRIVERKEY_NAME driverKeyNameW = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)malloc(nBytes); driverKeyNameW->ActualLength = nBytes; + driverKeyNameW->ConnectionIndex = index; success = DeviceIoControl(device, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, driverKeyNameW, diff --git a/Frasy/src/utils/usb_enumerator/details/get_external_hub_name.h b/Frasy/src/utils/usb_enumerator/details/get_external_hub_name.h new file mode 100644 index 0000000..63bc6e0 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_external_hub_name.h @@ -0,0 +1,70 @@ +/** + * @file get_external_hub_name.h + * @author Sam Martel + * @date 2026-03-02 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_EXTERNAL_HUB_NAME_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_EXTERNAL_HUB_NAME_H + +#include "oops.h" +#include "Brigerad/Utils/types/wstring_to_utf8.h" + +#include +#include + +namespace Frasy::Usb::Details { +inline std::optional GetExternalHubName(HANDLE device, uint8_t port) +{ + USB_NODE_CONNECTION_NAME extHubName; + // Get the length of the name of the external hub attached to the specified port. + extHubName.ConnectionIndex = port; + ULONG nBytes = 0; + BOOL success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_NAME, + &extHubName, + sizeof(extHubName), + &extHubName, + sizeof(extHubName), + &nBytes, + nullptr); + if (success == FALSE || extHubName.ActualLength <= sizeof(extHubName)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + // Allocate space to hold the external hub name. + nBytes = extHubName.ActualLength; + PUSB_NODE_CONNECTION_NAME extHubNameW = (PUSB_NODE_CONNECTION_NAME)malloc(nBytes); + extHubNameW->ConnectionIndex = port; + success = DeviceIoControl(device, + IOCTL_USB_GET_NODE_CONNECTION_NAME, + extHubNameW, + nBytes, + extHubNameW, + nBytes, + &nBytes, + nullptr); + auto wname = std::wstring(extHubNameW->NodeName); + free(extHubNameW); + if (success == FALSE) { + FRASY_USB_OOPS(); + return std::nullopt; + } + return wstring_to_utf8(wname); +} +} + +#endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_EXTERNAL_HUB_NAME_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_string_descriptor.h b/Frasy/src/utils/usb_enumerator/details/get_string_descriptor.h new file mode 100644 index 0000000..db2924d --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_string_descriptor.h @@ -0,0 +1,105 @@ +/** + * @file get_string_descriptor.h + * @author Sam Martel + * @date 2026-03-02 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTOR_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTOR_H + +#include "../string_descriptor.h" +#include "oops.h" + +#include +#include + +namespace Frasy::Usb::Details { +inline std::optional GetStringDescriptor(HANDLE device, + ULONG connIndex, + uint8_t descIndex, + uint16_t languageId) +{ + alignas(USB_DESCRIPTOR_REQUEST) uint8_t stringDescReqBuff[ + sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH]; + auto* stringDescReq = reinterpret_cast(stringDescReqBuff); + auto* stringDesc = reinterpret_cast(stringDescReqBuff + sizeof(USB_DESCRIPTOR_REQUEST)); + ULONG nBytes = sizeof(stringDescReqBuff); + // zero-fill the entire request structure. + memset(stringDescReqBuff, 0, nBytes); + + // Indicate the port from which the descriptor will be requested. + stringDescReq->ConnectionIndex = connIndex; + + // USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this + // IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request. + // + // USBD will automatically initialize these fields: + // bmRequest = 0x80 + // bRequest = 0x06 + // + // We must inititialize these fields: + // wValue = Descriptor Type (high) and Descriptor Index (low byte) + // wIndex = Zero (or Language ID for String Descriptors) + // wLength = Length of descriptor buffer + stringDescReq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | descIndex; + stringDescReq->SetupPacket.wIndex = languageId; + stringDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST)); + + // Now issue the get descriptor request. + ULONG nBytesReturned = 0; + BOOL success = DeviceIoControl(device, + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, + stringDescReq, + nBytes, + stringDescReq, + nBytes, + &nBytesReturned, + nullptr); + + // Do some sanity checks on the return from the get descriptor request. + if (success == FALSE) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + if (nBytesReturned < 2) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + if (stringDesc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + if (stringDesc->bLength != nBytesReturned - sizeof(USB_DESCRIPTOR_REQUEST)) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + if (stringDesc->bLength % 2 != 0) { + FRASY_USB_OOPS(); + return std::nullopt; + } + + // All is good! Convert to the output. + return StringDescriptorNode{ + .descriptorIndex = descIndex, + .languageId = languageId, + .stringDescriptor = stringDesc,}; +} +} // namespace Frasy::Usb::Details + +#endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTOR_H diff --git a/Frasy/src/utils/usb_enumerator/details/get_string_descriptors.h b/Frasy/src/utils/usb_enumerator/details/get_string_descriptors.h new file mode 100644 index 0000000..f67a853 --- /dev/null +++ b/Frasy/src/utils/usb_enumerator/details/get_string_descriptors.h @@ -0,0 +1,46 @@ +/** + * @file get_string_descriptors.h + * @author Sam Martel + * @date 2026-03-02 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTORS_H +#define FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTORS_H + +#include "string_descriptor.h" +#include "get_string_descriptor.h" + +#include + +namespace Frasy::Usb::Details { +inline std::vector GetStringDescriptors(HANDLE device, ULONG port, uint8_t descIndex, const std::wstring& languageIds) +{ + // TODO Windows has a cache here that it reads from + // https://github.com/microsoft/Windows-driver-samples/blob/main/usb/usbview/enum.c#L2703 + + // Get the next string descriptor. If empty, then we're done. + std::vector stringDescriptors; + stringDescriptors.reserve(languageIds.size()); + for (wchar_t languageId : languageIds) { + auto maybeStringDescriptor = GetStringDescriptor(device, port, descIndex, languageId); + if (!maybeStringDescriptor.has_value()) { break; } + stringDescriptors.push_back(maybeStringDescriptor.value()); + } + + return stringDescriptors; +} +} + +#endif //FRASY_UTILS_USB_ENUMERATOR_DETAILS_GET_STRING_DESCRIPTORS_H diff --git a/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h b/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h index 37d065e..36cebc3 100644 --- a/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h +++ b/Frasy/src/utils/usb_enumerator/details/usb_descriptor_request.h @@ -48,7 +48,7 @@ struct UsbDescriptorRequest { PUSB_CONFIGURATION_DESCRIPTOR ConfigDesc() { - BR_ASSERT(Data.size() < sizeof(USB_CONFIGURATION_DESCRIPTOR), "Not a config descriptor!"); + BR_ASSERT(Data.size() >= sizeof(USB_CONFIGURATION_DESCRIPTOR), "Not a config descriptor!"); return reinterpret_cast(Data.data()); } diff --git a/Frasy/src/utils/usb_enumerator/external_hub_info.h b/Frasy/src/utils/usb_enumerator/external_hub_info.h index 54fbd98..40afe1b 100644 --- a/Frasy/src/utils/usb_enumerator/external_hub_info.h +++ b/Frasy/src/utils/usb_enumerator/external_hub_info.h @@ -30,18 +30,19 @@ #include #include -namespace Frasy::Usb { -struct ExternalHubInfo: public DevicePnpStrings, DeviceInfoNode { - USB_NODE_INFORMATION hubInfo; - USB_HUB_INFORMATION_EX hubInfoEx; - std::string hubName; - UsbNodeConnectionInformationEx connectionInfo; - UsbPortConnectorProperties portConnectorProps; - UsbDescriptorRequest configDesc; - UsbDescriptorRequest bosDesc; - std::vector stringDescriptors; +namespace Frasy::Usb { +struct ExternalHubInfo : public DevicePnpStrings, DeviceInfoNode { + std::string leafName; + USB_NODE_INFORMATION hubInfo; + USB_HUB_INFORMATION_EX hubInfoEx; + std::string hubName; + UsbNodeConnectionInformationEx connectionInfo; + UsbPortConnectorProperties portConnectorProps; + UsbDescriptorRequest configDesc; + UsbDescriptorRequest bosDesc; + std::vector stringDescriptors; std::optional connectionInfoV2; // Not present if root HUB. - USB_HUB_CAPABILITIES_EX hubCapabilitiesEx; + USB_HUB_CAPABILITIES_EX hubCapabilitiesEx; }; } diff --git a/Frasy/src/utils/usb_enumerator/pnp_strings.h b/Frasy/src/utils/usb_enumerator/pnp_strings.h index a828f7e..d048503 100644 --- a/Frasy/src/utils/usb_enumerator/pnp_strings.h +++ b/Frasy/src/utils/usb_enumerator/pnp_strings.h @@ -29,6 +29,7 @@ struct DevicePnpStrings { std::string service; std::string deviceClass; std::string powerState; + std::string friendlyName; void setDevicePnpStrings(const DevicePnpStrings& other) { @@ -38,6 +39,7 @@ struct DevicePnpStrings { service = other.service; deviceClass = other.deviceClass; powerState = other.powerState; + friendlyName = other.friendlyName; } }; } diff --git a/Frasy/src/utils/usb_enumerator/root_hub_info.h b/Frasy/src/utils/usb_enumerator/root_hub_info.h index 0b12d0f..339b7b3 100644 --- a/Frasy/src/utils/usb_enumerator/root_hub_info.h +++ b/Frasy/src/utils/usb_enumerator/root_hub_info.h @@ -31,6 +31,7 @@ namespace Frasy::Usb { struct RootHubInfo : public DevicePnpStrings, DeviceInfoNode { + std::string leafName; USB_NODE_INFORMATION hubInfo; USB_HUB_INFORMATION_EX hubInfoEx; std::string hubName; diff --git a/Frasy/src/utils/usb_enumerator/string_descriptor.h b/Frasy/src/utils/usb_enumerator/string_descriptor.h index 2ecd2c2..807f281 100644 --- a/Frasy/src/utils/usb_enumerator/string_descriptor.h +++ b/Frasy/src/utils/usb_enumerator/string_descriptor.h @@ -19,16 +19,53 @@ #ifndef FRASY_UTILS_USB_ENUMERATOR_STRING_DESCRIPTOR_H #define FRASY_UTILS_USB_ENUMERATOR_STRING_DESCRIPTOR_H +#include + #include +#include #include #include namespace Frasy::Usb { struct StringDescriptor { - uint8_t descriptorIndex; - USHORT languageId; - USB_STRING_DESCRIPTOR stringDescriptor[1]; + StringDescriptor() = default; + StringDescriptor(const StringDescriptor&) = default; + StringDescriptor(StringDescriptor&&) = default; + StringDescriptor& operator=(const StringDescriptor&) = default; + StringDescriptor& operator=(StringDescriptor&&) = default; + ~StringDescriptor() = default; + + StringDescriptor(const USB_STRING_DESCRIPTOR* desc) + : length(desc->bLength), + descriptorType(desc->bDescriptorType), + string(desc->bString) + { + toUtf8(); + } + + const std::string& toUtf8() const { return utf8; } + + const std::string& toUtf8() + { + if (utf8.empty() && !string.empty()) { + utf8 = wstring_to_utf8(string); + } + return utf8; + } + + uint8_t length = 0; + uint8_t descriptorType = 0; + std::wstring string; + +private: + std::string utf8; +}; + +struct StringDescriptorNode { + uint8_t descriptorIndex = 0; + USHORT languageId = 0; + StringDescriptor stringDescriptor; }; } diff --git a/Frasy/src/utils/usb_enumerator/usb_device_info.h b/Frasy/src/utils/usb_enumerator/usb_device_info.h index 81be3eb..5ca6a31 100644 --- a/Frasy/src/utils/usb_enumerator/usb_device_info.h +++ b/Frasy/src/utils/usb_enumerator/usb_device_info.h @@ -31,16 +31,14 @@ namespace Frasy::Usb { struct DeviceInfo : DeviceInfoNode, DevicePnpStrings { - std::optional hubInfo; // Only if HUB - std::optional hubInfoEx; // Only if HUB - std::optional hubName; // Only if HUB - std::optional connectionInfo; // Not present if root hub + std::string leafName; + UsbNodeConnectionInformationEx connectionInfo; UsbPortConnectorProperties portConnectorProps; - std::optional configDesc; // Not present if root hub - std::optional bosDesc; // Not present if root hub - std::vector stringDescriptors; - USB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2; // Not present if root hub - std::optional hubCapabilitiesEx; // Only if not a hub. + UsbDescriptorRequest configDesc; + UsbDescriptorRequest bosDesc; + std::vector stringDescriptors; + USB_NODE_CONNECTION_INFORMATION_EX_V2 connectionInfoV2; + std::optional hubCapabilitiesEx; }; } diff --git a/Frasy/src/utils/usb_enumerator/usb_enumerator.cpp b/Frasy/src/utils/usb_enumerator/usb_enumerator.cpp index a011d7d..1b1aaa3 100644 --- a/Frasy/src/utils/usb_enumerator/usb_enumerator.cpp +++ b/Frasy/src/utils/usb_enumerator/usb_enumerator.cpp @@ -90,12 +90,12 @@ std::optional EnumerateUsbHostController(HANDLE deviceHandle, // Get bus, device and function hcInfo.busDeviceFunctionValid = false; BOOL success = SetupDiGetDeviceRegistryPropertyA(deviceInfo, - deviceInfoData, - SPDRP_BUSNUMBER, - nullptr, - (PBYTE)&hcInfo.busNumber, - sizeof(hcInfo.busNumber), - nullptr); + deviceInfoData, + SPDRP_BUSNUMBER, + nullptr, + (PBYTE)&hcInfo.busNumber, + sizeof(hcInfo.busNumber), + nullptr); ULONG deviceAndFunction = 0; if (success == TRUE) { success = SetupDiGetDeviceRegistryPropertyA(deviceInfo, @@ -131,14 +131,16 @@ std::optional EnumerateUsbHostController(HANDLE deviceHandle, std::vector EnumerateUsbHostControllers() { + using namespace std::chrono; + auto start = steady_clock::now(); std::vector controllers; // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-host-controller GUID usbHostControllerGuid = GUID{0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27}}; HDEVINFO deviceInfo = SetupDiGetClassDevs(&usbHostControllerGuid, - nullptr, - nullptr, - (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); + nullptr, + nullptr, + (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); SP_DEVINFO_DATA deviceInfoData; deviceInfoData.cbSize = sizeof(deviceInfoData); @@ -154,11 +156,11 @@ std::vector EnumerateUsbHostControllers() &deviceInterfaceData) != 0; ++devInterfaceIndex) { ULONG requiredLength = 0; BOOL success = SetupDiGetDeviceInterfaceDetail(deviceInfo, - &deviceInterfaceData, - nullptr, - 0, - &requiredLength, - nullptr); + &deviceInterfaceData, + nullptr, + 0, + &requiredLength, + nullptr); if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { FRASY_USB_OOPS(); break; @@ -172,11 +174,11 @@ std::vector EnumerateUsbHostControllers() } deviceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); success = SetupDiGetDeviceInterfaceDetail(deviceInfo, - &deviceInterfaceData, - deviceDetailData, - requiredLength, - &requiredLength, - nullptr); + &deviceInterfaceData, + deviceDetailData, + requiredLength, + &requiredLength, + nullptr); if (!success) { FRASY_USB_OOPS(); } @@ -205,7 +207,9 @@ std::vector EnumerateUsbHostControllers() SetupDiDestroyDeviceInfoList(deviceInfo); - BR_CORE_DEBUG("Enumerated {} USB Host Controllers", controllers.size()); + BR_CORE_DEBUG("Enumerated {} USB Host Controllers in {}ms", + controllers.size(), + duration_cast(steady_clock::now() - start).count()); return controllers; } } From e50cebd2619f915065e839e72ff392c6f0ce1d5e Mon Sep 17 00:00:00 2001 From: Sam Martel Date: Tue, 3 Mar 2026 12:00:55 -0500 Subject: [PATCH 3/3] Add a viewer to help with further development --- Frasy/src/layers/main_application_layer.cpp | 38 ++--- Frasy/src/layers/main_application_layer.h | 2 + Frasy/src/layers/usb_tree_viewer.cpp | 176 ++++++++++++++++++++ Frasy/src/layers/usb_tree_viewer.h | 50 ++++++ 4 files changed, 242 insertions(+), 24 deletions(-) create mode 100644 Frasy/src/layers/usb_tree_viewer.cpp create mode 100644 Frasy/src/layers/usb_tree_viewer.h diff --git a/Frasy/src/layers/main_application_layer.cpp b/Frasy/src/layers/main_application_layer.cpp index e31fccc..1cda654 100644 --- a/Frasy/src/layers/main_application_layer.cpp +++ b/Frasy/src/layers/main_application_layer.cpp @@ -47,6 +47,7 @@ void MainApplicationLayer::onAttach() m_resultViewer = std::make_unique(); m_resultAnalyzer = std::make_unique(); m_testViewer = std::make_unique(); + m_usbTreeViewer = std::make_unique(); m_testViewer->SetInterface(this); bool maximized = Interpreter::Get().getConfig().value("maximized", true); @@ -62,6 +63,7 @@ void MainApplicationLayer::onAttach() m_resultViewer->onAttach(); m_resultAnalyzer->onAttach(); m_testViewer->onAttach(); + m_usbTreeViewer->onAttach(); m_resultAnalyzer->setGetTitle([this] { return m_orchestrator.getTitle(); }); m_orchestrator.setCanOpen(&m_canOpen); @@ -80,6 +82,7 @@ void MainApplicationLayer::onDetach() m_resultViewer->onDetach(); m_testViewer->onDetach(); m_resultAnalyzer->onDetach(); + m_usbTreeViewer->onDetach(); } @@ -100,6 +103,7 @@ void MainApplicationLayer::onUpdate(Brigerad::Timestep ts) m_resultViewer->onUpdate(ts); m_testViewer->onUpdate(ts); m_resultAnalyzer->onUpdate(ts); + m_usbTreeViewer->onUpdate(ts); } @@ -122,6 +126,7 @@ void MainApplicationLayer::onImGuiRender() if (ImGui::MenuItem("Test Viewer", "F6")) { makeTestViewerVisible(); } if (ImGui::MenuItem("CANopen Viewer", "F7")) { makeCanOpenViewerVisible(); } if (ImGui::MenuItem("Lua Profiler", "F8")) { m_renderProfiler = true; } + if (ImGui::MenuItem("USB Tree Viewer")) { m_usbTreeViewer->SetVisibility(true); } ImGui::Separator(); if (m_noMove && ImGui::MenuItem("Unlock")) { m_noMove = false; } if (!m_noMove && ImGui::MenuItem("Lock")) { m_noMove = true; } @@ -151,6 +156,7 @@ void MainApplicationLayer::onImGuiRender() m_resultViewer->onImGuiRender(); m_resultAnalyzer->onImGuiRender(); m_testViewer->onImGuiRender(); + m_usbTreeViewer->onImGuiRender(); m_orchestrator.renderPopups(); handleResultAnalyserPopup(); @@ -180,34 +186,22 @@ void MainApplicationLayer::onEvent(Brigerad::Event& e) } void MainApplicationLayer::makeLogWindowVisible() -{ - m_logWindow->SetVisibility(true); -} +{ m_logWindow->SetVisibility(true); } void MainApplicationLayer::makeDeviceViewerVisible() -{ - m_deviceViewer->setVisibility(true); -} +{ m_deviceViewer->setVisibility(true); } void MainApplicationLayer::makeCanOpenViewerVisible() -{ - m_canOpenViewer->setVisibility(true); -} +{ m_canOpenViewer->setVisibility(true); } void MainApplicationLayer::makeResultViewerVisible() -{ - m_resultViewer->setVisibility(true); -} +{ m_resultViewer->setVisibility(true); } void MainApplicationLayer::makeResultAnalyzerVisible() -{ - m_resultAnalyzer->setVisibility(true); -} +{ m_resultAnalyzer->setVisibility(true); } void MainApplicationLayer::makeTestViewerVisible() -{ - m_testViewer->SetVisibility(true); -} +{ m_testViewer->SetVisibility(true); } void MainApplicationLayer::renderAbout() { @@ -539,12 +533,8 @@ void MainApplicationLayer::generate() // m_orchestrator->Generate(); } void MainApplicationLayer::setTestEnable(const std::string& sequence, const std::string& test, bool enable) -{ - m_orchestrator.setTestEnable(sequence, test, enable); -} +{ m_orchestrator.setTestEnable(sequence, test, enable); } void MainApplicationLayer::setSequenceEnable(const std::string& sequence, bool enable) -{ - m_orchestrator.setSequenceEnable(sequence, enable); -} +{ m_orchestrator.setSequenceEnable(sequence, enable); } } // namespace Frasy diff --git a/Frasy/src/layers/main_application_layer.h b/Frasy/src/layers/main_application_layer.h index 5454537..1ed927d 100644 --- a/Frasy/src/layers/main_application_layer.h +++ b/Frasy/src/layers/main_application_layer.h @@ -21,6 +21,7 @@ # include "result_analyzer.h" # include "result_viewer.h" # include "test_viewer.h" +# include "usb_tree_viewer.h" # include "utils/communication/can_open/can_open.h" # include "utils/lua/orchestrator/orchestrator.h" @@ -93,6 +94,7 @@ class MainApplicationLayer : public Brigerad::Layer, public TestViewer::Interfac std::unique_ptr m_resultViewer = nullptr; std::unique_ptr m_resultAnalyzer = nullptr; std::unique_ptr m_testViewer = nullptr; + std::unique_ptr m_usbTreeViewer = nullptr; Brigerad::Ref m_run; Brigerad::Ref m_runWarn; diff --git a/Frasy/src/layers/usb_tree_viewer.cpp b/Frasy/src/layers/usb_tree_viewer.cpp new file mode 100644 index 0000000..5268026 --- /dev/null +++ b/Frasy/src/layers/usb_tree_viewer.cpp @@ -0,0 +1,176 @@ +/** + * @file usb_tree_viewer.cpp + * @author Sam Martel + * @date 2026-03-03 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ +#include "usb_tree_viewer.h" + +#include "utils/misc/visit.h" +#include "utils/usb_enumerator/usb_enumerator.h" + +#include + +namespace Frasy { + +namespace { +void RenderNodes(const std::vector& tree); + +struct RenderRootHubInfo { + void operator()(const Usb::RootHubInfo& info) const + { + bool hovered = false; + if (ImGui::TreeNodeEx(&info, ImGuiTreeNodeFlags_DefaultOpen, "Root Hub: %s", info.hubName.c_str())) { + hovered = ImGui::IsItemHovered(); + ImGui::BulletText("%d Nodes", info.nodes.size()); + RenderNodes(info.nodes); + + ImGui::TreePop(); + } + else { + hovered = ImGui::IsItemHovered(); + } + + if (hovered) { + ImGui::BeginTooltip(); + ImGui::Text("Hub Name: %s", info.leafName.c_str()); + ImGui::EndTooltip(); + } + } +}; + +struct RenderHostControllerInfo { + void operator()(const Usb::HostControllerInfo& info) const + { + bool hovered = false; + if (ImGui::TreeNodeEx(&info, + ImGuiTreeNodeFlags_DefaultOpen, + "Host Controller: %s - %s", + info.deviceDesc.c_str(), + info.driverKey.c_str())) { + hovered = ImGui::IsItemHovered(); + RenderRootHubInfo {}(info.rootHub); + ImGui::TreePop(); + } + else { + hovered = ImGui::IsItemHovered(); + } + if (hovered) { + ImGui::BeginTooltip(); + ImGui::Text("VendorID: %d, DeviceID: %d, SubSysId: %d, Revision: %d", + info.vendorId, + info.deviceId, + info.subSysId, + info.revision); + if (!info.busDeviceFunctionValid) { ImGui::Text("Invalid bus/device/function"); } + else { + ImGui::Text( + "Bus number: %d, device: %d, function: %d", info.busNumber, info.busDevice, info.busFunction); + } + ImGui::EndTooltip(); + } + } +}; + +void RenderStringDescriptors(const Usb::UsbNodeConnectionInformationEx& connInfo, + const std::vector& descriptors) +{ + ImGui::BulletText("# of supported configurations: %d", connInfo.DeviceDescriptor.bNumConfigurations); + ImGui::BulletText("# of endpoints: %d", connInfo.NumberOfOpenPipes); + + auto renderDesc = [&](std::string_view name, uint8_t idx) { + std::string_view str = "Absent"; + if (idx != 0 && idx < descriptors.size()) { str = descriptors[idx].stringDescriptor.toUtf8(); } + ImGui::BulletText("%s: %s", name.data(), str.data()); + }; + renderDesc("Manufacturer", connInfo.DeviceDescriptor.iManufacturer); + renderDesc("Product", connInfo.DeviceDescriptor.iProduct); + renderDesc("Serial Number", connInfo.DeviceDescriptor.iSerialNumber); + renderDesc("Device Class", connInfo.DeviceDescriptor.bDeviceClass); + // The first descriptor is the language ID descriptors. + for (auto& descriptor : descriptors | std::views::drop(1)) { + ImGui::BulletText("Descriptor Index: %d, language ID: %d, descType: %d, data: %s", + descriptor.descriptorIndex, + descriptor.languageId, + descriptor.stringDescriptor.descriptorType, + descriptor.stringDescriptor.toUtf8().c_str()); + } +} + +struct RenderExternalHubInfo { + void operator()(const Usb::ExternalHubInfo& info) const + { + bool hovered = false; + if (ImGui::TreeNodeEx(&info, ImGuiTreeNodeFlags_DefaultOpen, "%s", info.leafName.c_str())) { + hovered = ImGui::IsItemHovered(); + ImGui::BulletText("%d Nodes", info.nodes.size()); + RenderNodes(info.nodes); + + ImGui::TreePop(); + } + else { + hovered = ImGui::IsItemHovered(); + } + + if (hovered) { + ImGui::BeginTooltip(); + ImGui::Text("Hub Name: %s", info.hubName.c_str()); + RenderStringDescriptors(info.connectionInfo, info.stringDescriptors); + ImGui::EndTooltip(); + } + } +}; + +struct RenderDeviceInfo { + void operator()(const Usb::DeviceInfo& info) const + { + ImGui::BulletText("%s", info.leafName.c_str()); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + RenderStringDescriptors(info.connectionInfo, info.stringDescriptors); + ImGui::EndTooltip(); + } + } +}; + +void RenderNodes(const std::vector& tree) +{ + for (const auto& node : tree) { + visit(node, RenderHostControllerInfo {}, RenderRootHubInfo {}, RenderExternalHubInfo {}, RenderDeviceInfo {}); + } +} +} // namespace + +UsbTreeViewer::UsbTreeViewer() : Layer("UsbTreeViewer") +{ +} + +void UsbTreeViewer::onImGuiRender() +{ + if (!m_isVisible) { return; } + + if (ImGui::Begin("USB Tree", &m_isVisible)) { + bool isRefreshing = m_isRefreshing; // To avoid TOCTOU :D + ImGui::Text("%d nodes found %s", m_nodes.size(), isRefreshing ? "(Refreshing...)" : ""); + if (ImGui::Button("Refresh")) { + m_refreshFuture = std::async(std::launch::async, [this] { + m_isRefreshing = true; + m_nodes = Usb::EnumerateUsbTree(); + m_isRefreshing = false; + }); + } + if (!isRefreshing) { RenderNodes(m_nodes); } + } + ImGui::End(); +} +} // namespace Frasy diff --git a/Frasy/src/layers/usb_tree_viewer.h b/Frasy/src/layers/usb_tree_viewer.h new file mode 100644 index 0000000..a2c02c3 --- /dev/null +++ b/Frasy/src/layers/usb_tree_viewer.h @@ -0,0 +1,50 @@ +/** + * @file usb_tree_viewer.h + * @author Sam Martel + * @date 2026-03-03 + * @brief + * + * @copyright + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. If + * not, see . + */ + + +#ifndef FRASY_SRC_LAYERS_USB_TREE_VIEWER_H +#define FRASY_SRC_LAYERS_USB_TREE_VIEWER_H +#include "Brigerad/Core/Layer.h" + +#include "utils/usb_enumerator/external_hub_info.h" +#include "utils/usb_enumerator/host_controller_info.h" +#include "utils/usb_enumerator/root_hub_info.h" +#include "utils/usb_enumerator/usb_device_info.h" +#include "utils/usb_enumerator/usb_node.h" + +#include +#include + +namespace Frasy { +class UsbTreeViewer : public Brigerad::Layer { +public: + UsbTreeViewer(); + ~UsbTreeViewer() override = default; + + void onImGuiRender() override; + + void SetVisibility(bool visibility) { m_isVisible = visibility; } + +private: + bool m_isVisible = false; + std::atomic m_isRefreshing = false; + std::future m_refreshFuture; + std::vector m_nodes; +}; +} // namespace Frasy + +#endif // FRASY_SRC_LAYERS_USB_TREE_VIEWER_H