From 46df381a46122cae35e6ddb56926a28ec113e126 Mon Sep 17 00:00:00 2001 From: Marius Greuel Date: Sat, 22 Feb 2020 21:17:41 +0200 Subject: [PATCH] Add support for COM port discovery via USB VID/PID Add support for Leonardo USB bootloader auto-reset --- avrdude.vcxproj | 3 + avrdude.vcxproj.filters | 9 + msvc/usb_com_helper.cpp | 82 +++ msvc/usb_com_helper.h | 35 ++ msvc/usb_com_locator.h | 1074 +++++++++++++++++++++++++++++++++++++++ ser_win32.c | 11 + 6 files changed, 1214 insertions(+) create mode 100644 msvc/usb_com_helper.cpp create mode 100644 msvc/usb_com_helper.h create mode 100644 msvc/usb_com_locator.h diff --git a/avrdude.vcxproj b/avrdude.vcxproj index dde4354c..97dbae71 100644 --- a/avrdude.vcxproj +++ b/avrdude.vcxproj @@ -197,6 +197,7 @@ + @@ -249,6 +250,8 @@ + + diff --git a/avrdude.vcxproj.filters b/avrdude.vcxproj.filters index 140281c0..12e01d78 100644 --- a/avrdude.vcxproj.filters +++ b/avrdude.vcxproj.filters @@ -176,6 +176,9 @@ 4 msvc + + 4 msvc + @@ -328,5 +331,11 @@ 4 msvc + + 4 msvc + + + 4 msvc + \ No newline at end of file diff --git a/msvc/usb_com_helper.cpp b/msvc/usb_com_helper.cpp new file mode 100644 index 00000000..9a718635 --- /dev/null +++ b/msvc/usb_com_helper.cpp @@ -0,0 +1,82 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2019 Marius Greuel + * + * 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 2 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 . + */ + +extern "C" +{ +#include "avrdude.h" +} + +#include "usb_com_helper.h" +#include "usb_com_locator.h" + +class AvrdudeConsole : public UsbComLocator::IConsole +{ +public: + void WriteLine(UsbComLocator::MessageLevel level, const std::string& message) override + { + switch (level) + { + case UsbComLocator::MessageLevel::Verbose: + avrdude_message(MSG_NOTICE, "%s: %s\n", progname, message.c_str()); + break; + case UsbComLocator::MessageLevel::Debug: + avrdude_message(MSG_DEBUG, "%s: %s\n", progname, message.c_str()); + break; + default: + avrdude_message(MSG_INFO, "%s: %s\n", progname, message.c_str()); + break; + } + } +}; + +int win32_find_usb_com_port(const char* deviceId, char** port, bool wait_for_device, bool find_related_devices, bool auto_reset) +{ + try + { + UsbComLocator::Locator::Options options; + options.WaitForDevice = wait_for_device; + options.FindRelatedDevices = find_related_devices; + options.AutoReset = auto_reset; + + AvrdudeConsole console; + UsbComLocator::Locator locator(&console); + std::string filename = locator.FindPortForDevice(deviceId, &options); + if (filename.empty()) + { + *port = nullptr; + return -1; + } + + filename = "\\\\.\\" + filename; + + size_t bufferSize = filename.size() + 1; + *port = static_cast(malloc(bufferSize)); + if (*port == nullptr) + { + return -1; + } + + strncpy(*port, filename.c_str(), bufferSize); + return 0; + } + catch (const std::exception&) + { + *port = nullptr; + return -1; + } +} diff --git a/msvc/usb_com_helper.h b/msvc/usb_com_helper.h new file mode 100644 index 00000000..9dcd19a9 --- /dev/null +++ b/msvc/usb_com_helper.h @@ -0,0 +1,35 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2019 Marius Greuel + * + * 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 2 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 USB_COM_HELPER_H +#define USB_COM_HELPER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int win32_find_usb_com_port(const char* deviceId, char** port, bool wait_for_device, bool find_related_devices, bool auto_reset); + +#ifdef __cplusplus +} +#endif + +#endif // USB_COM_HELPER_H diff --git a/msvc/usb_com_locator.h b/msvc/usb_com_locator.h new file mode 100644 index 00000000..13323d80 --- /dev/null +++ b/msvc/usb_com_locator.h @@ -0,0 +1,1074 @@ +// +// usb_com_locator.h +// Copyright (C) 2019 Marius Greuel. All rights reserved. +// + +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "setupapi.lib") + +namespace UsbComLocator +{ + enum class MessageLevel + { + Error, + Info, + Verbose, + Debug, + }; + + struct IConsole + { + virtual void WriteLine(MessageLevel level, const std::string& message) = 0; + }; + + class Win32 + { + public: + static std::wstring CreateMessageFromHResult(long hr) + { + std::wstring message; + + LPWSTR pszMessage = nullptr; + DWORD dwChars = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, hr, 0, (LPWSTR)&pszMessage, 0, nullptr); + if (dwChars > 0 && pszMessage != nullptr) + { + if (dwChars > 0 && pszMessage[dwChars - 1] == '\n') + dwChars--; + + if (dwChars > 0 && pszMessage[dwChars - 1] == '\r') + dwChars--; + + message = std::wstring(pszMessage, dwChars); + + LocalFree(pszMessage); + } + + return message; + } + + }; + + class Handle + { + public: + Handle() = default; + + ~Handle() + { + Close(); + } + + Handle(const Handle&) = delete; + Handle& operator=(const Handle& other) = delete; + Handle(Handle&&) = default; + Handle& operator=(Handle&& other) = default; + + operator HANDLE() const noexcept + { + return m_handle; + } + + Handle& operator=(HANDLE handle) + { + Close(); + m_handle = handle; + return *this; + } + + void Close() noexcept + { + if (m_handle != nullptr) + { + CloseHandle(m_handle); + m_handle = nullptr; + } + } + + void Attach(HANDLE handle) noexcept + { + assert(m_handle == nullptr); + m_handle = handle; + } + + HANDLE Detach() noexcept + { + HANDLE handle = m_handle; + m_handle = nullptr; + return handle; + } + + protected: + HANDLE m_handle = nullptr; + }; + + class Encoding + { + public: + static std::string ToAnsi(const std::wstring& text) + { + std::string str; + + if (!text.empty()) + { + int chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast(text.size()), nullptr, 0, nullptr, nullptr); + if (chars == 0) + { + return std::string(); + } + + str.resize(chars); + chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast(text.size()), &str[0], static_cast(str.size()), nullptr, nullptr); + if (chars == 0) + { + return std::string(); + } + } + + return str; + } + + static std::wstring ToUnicode(const std::string& text) + { + std::wstring str; + + if (!text.empty()) + { + int chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast(text.size()), nullptr, 0); + if (chars == 0) + { + return std::wstring(); + } + + str.resize(chars); + chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast(text.size()), &str[0], static_cast(str.size())); + if (chars == 0) + { + return std::wstring(); + } + } + + return str; + } + }; + + class RegistryKey + { + public: + RegistryKey() = default; + + RegistryKey(HKEY hKey) : m_hKey(hKey) + { + } + + ~RegistryKey() + { + Close(); + } + + RegistryKey(const RegistryKey&) = delete; + RegistryKey& operator=(const RegistryKey& other) = delete; + RegistryKey(RegistryKey&&) = default; + RegistryKey& operator=(RegistryKey&& other) = default; + + operator HKEY() const noexcept + { + return m_hKey; + } + + void Close() noexcept + { + if (m_hKey != nullptr) + { + RegCloseKey(m_hKey); + m_hKey = nullptr; + } + } + + void Attach(HKEY handle) noexcept + { + assert(m_hKey == nullptr); + m_hKey = handle; + } + + HKEY Detach() noexcept + { + HKEY handle = m_hKey; + m_hKey = nullptr; + return handle; + } + + HRESULT QueryValue(LPCWSTR pszValueName, std::wstring& value) noexcept + { + DWORD dwType = 0; + DWORD dwRequiredSize = 0; + LSTATUS nError = RegQueryValueExW(m_hKey, pszValueName, nullptr, &dwType, nullptr, &dwRequiredSize); + if (nError != ERROR_SUCCESS) + { + return HRESULT_FROM_WIN32(nError); + } + + if (dwType != REG_SZ && dwType != REG_EXPAND_SZ) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + std::vector buffer(dwRequiredSize); + nError = RegQueryValueExW(m_hKey, pszValueName, nullptr, &dwType, buffer.data(), &dwRequiredSize); + if (nError != ERROR_SUCCESS) + { + return HRESULT_FROM_WIN32(nError); + } + + LPCWSTR pszValue = reinterpret_cast(buffer.data()); + DWORD dwChars = dwRequiredSize / sizeof(WCHAR); + if (dwChars > 0 && pszValue[dwChars - 1] == 0) + { + dwChars--; + } + + value = std::wstring(pszValue, dwChars); + return S_OK; + } + + protected: + HKEY m_hKey = nullptr; + }; + + class DeviceInfo : public SP_DEVINFO_DATA + { + public: + DeviceInfo() + { + (SP_DEVINFO_DATA&)(*this) = { sizeof(SP_DEVINFO_DATA) }; + } + + DeviceInfo(const DeviceInfo&) = delete; + DeviceInfo& operator=(const DeviceInfo& other) = delete; + DeviceInfo(DeviceInfo&&) = delete; + DeviceInfo& operator=(DeviceInfo&& other) = delete; + + public: + HRESULT Open(HDEVINFO hDeviceInfoSet, LPCWSTR pszDeviceInstanceId, HWND hwndParent = nullptr, DWORD dwOpenFlags = 0) + { + if (!SetupDiOpenDeviceInfoW(hDeviceInfoSet, pszDeviceInstanceId, hwndParent, dwOpenFlags, this)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; + } + + HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, bool& value) + { + DEVPROP_BOOLEAN buffer{}; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + DWORD dwRequiredSize = 0; + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast(&buffer), sizeof(buffer), &dwRequiredSize, 0)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + if (type != DEVPROP_TYPE_BOOLEAN) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); + } + + value = buffer != DEVPROP_FALSE; + return S_OK; + } + + HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, uint32_t& value) + { + uint32_t buffer{}; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + DWORD dwRequiredSize = 0; + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast(&buffer), sizeof(buffer), &dwRequiredSize, 0)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + if (type != DEVPROP_TYPE_UINT32) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); + } + + value = buffer; + return S_OK; + } + + HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, GUID& value) + { + GUID buffer{}; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + DWORD dwRequiredSize = 0; + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast(&buffer), sizeof(buffer), &dwRequiredSize, 0)) + { + DWORD dwError = GetLastError(); + if (dwError != ERROR_INSUFFICIENT_BUFFER) + { + return HRESULT_FROM_WIN32(dwError); + } + } + + if (type != DEVPROP_TYPE_GUID) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); + } + + value = buffer; + return S_OK; + } + + HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::wstring& value) + { + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + DWORD dwRequiredSize = 0; + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0)) + { + DWORD dwError = GetLastError(); + if (dwError != ERROR_INSUFFICIENT_BUFFER) + { + return HRESULT_FROM_WIN32(dwError); + } + } + + if (type != DEVPROP_TYPE_STRING) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); + } + + std::vector buffer(dwRequiredSize); + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + LPCWSTR pszValue = reinterpret_cast(buffer.data()); + DWORD dwChars = dwRequiredSize / sizeof(WCHAR); + if (dwChars > 0 && pszValue[dwChars - 1] == 0) + { + dwChars--; + } + + value = std::wstring(pszValue, dwChars); + return S_OK; + } + + HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::vector& values) + { + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + DWORD dwRequiredSize = 0; + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0)) + { + DWORD dwError = GetLastError(); + if (dwError != ERROR_INSUFFICIENT_BUFFER) + { + return HRESULT_FROM_WIN32(dwError); + } + } + + if (type != DEVPROP_TYPE_STRING_LIST) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE); + } + + std::vector buffer(dwRequiredSize); + if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0)) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + auto data = reinterpret_cast(buffer.data()); + while (*data != 0) + { + values.push_back(data); + data += wcslen(data) + 1; + } + + return S_OK; + } + + HRESULT GetRegistryValue(HDEVINFO hDeviceInfoSet, LPCWSTR pszValueName, std::wstring& value) + { + HKEY hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, this, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (hKey == INVALID_HANDLE_VALUE) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + RegistryKey key(hKey); + return key.QueryValue(pszValueName, value); + } + }; + + class DeviceInfoSet + { + public: + DeviceInfoSet() = default; + DeviceInfoSet(const DeviceInfoSet&) = delete; + DeviceInfoSet& operator=(const DeviceInfoSet& other) = delete; + DeviceInfoSet(DeviceInfoSet&&) = delete; + DeviceInfoSet& operator=(DeviceInfoSet&& other) = delete; + + ~DeviceInfoSet() + { + Destroy(); + } + + public: + operator HDEVINFO() const noexcept + { + return m_hDeviceInfoSet; + } + + public: + HRESULT Create(const GUID* classGuid, HWND hwndParent) + { + Destroy(); + + HDEVINFO hDeviceInfoSet = SetupDiCreateDeviceInfoList(classGuid, hwndParent); + if (hDeviceInfoSet == INVALID_HANDLE_VALUE) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + m_hDeviceInfoSet = hDeviceInfoSet; + return S_OK; + } + + HRESULT GetClassDevs(const GUID* classGuid, PCWSTR pszEnumerator, HWND hWndParent, DWORD dwFlags) + { + Destroy(); + + HDEVINFO hDeviceInfoSet = SetupDiGetClassDevsW(classGuid, pszEnumerator, hWndParent, dwFlags); + if (hDeviceInfoSet == INVALID_HANDLE_VALUE) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + m_hDeviceInfoSet = hDeviceInfoSet; + return S_OK; + } + + void Destroy() + { + if (m_hDeviceInfoSet != INVALID_HANDLE_VALUE) + { + SetupDiDestroyDeviceInfoList(m_hDeviceInfoSet); + m_hDeviceInfoSet = INVALID_HANDLE_VALUE; + } + } + + private: + HDEVINFO m_hDeviceInfoSet = INVALID_HANDLE_VALUE; + }; + + class Device + { + public: + Device() = default; + Device(const Device&) = delete; + Device& operator=(const Device& other) = delete; + Device(Device&&) = delete; + Device& operator=(Device&& other) = delete; + + public: + bool IsBootloaderMode() const + { + return !m_isCompositeDevice; + } + + HRESULT Open( + DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE, + DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL, + LPSECURITY_ATTRIBUTES lpSecurityAttributes = nullptr) + { + HANDLE hDevice = CreateFileW( + (L"\\\\.\\" + m_port).c_str(), + dwDesiredAccess, + 0, + lpSecurityAttributes, + OPEN_EXISTING, + dwFlagsAndAttributes, + nullptr); + if (hDevice == INVALID_HANDLE_VALUE) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + m_hDevice = hDevice; + return S_OK; + } + + void Close() + { + m_hDevice.Close(); + } + + HRESULT WaitForPortAvailability(uint32_t retries = 100, uint32_t timeout = 10) + { + while (true) + { + HRESULT hr = Open(); + if (SUCCEEDED(hr)) + { + Close(); + return S_OK; + } + + if (retries == 0) + { + return hr; + } + + Sleep(timeout); + retries--; + } + } + + HRESULT Reset() + { + HRESULT hr = Open(); + if (FAILED(hr)) + { + return hr; + } + + EscapeCommFunction(m_hDevice, CLRDTR); + + DCB dcb = { sizeof(DCB) }; + dcb.BaudRate = CBR_1200; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + SetCommState(m_hDevice, &dcb); + + Close(); + return S_OK; + } + + public: + std::wstring m_instanceId; + std::wstring m_port; + std::wstring m_location; + std::wstring m_productName; + bool m_isPresent = false; + bool m_isCompositeDevice = false; + uint16_t m_vid = 0; + uint16_t m_pid = 0; + Handle m_hDevice; + }; + + class Enumerator + { + public: + Enumerator() = default; + Enumerator(const Enumerator&) = delete; + Enumerator& operator=(const Enumerator& other) = delete; + Enumerator(Enumerator&&) = delete; + Enumerator& operator=(Enumerator&& other) = delete; + + public: + std::vector>& GetDevices() { return m_devices; } + + public: + HRESULT EnumerateDevices(bool includeUnpluggedDevices = false) + { + m_devices.clear(); + + DeviceInfoSet deviceInfoSet; + HRESULT hr = deviceInfoSet.GetClassDevs(&GUID_DEVINTERFACE_COMPORT, nullptr, nullptr, (includeUnpluggedDevices ? 0 : DIGCF_PRESENT) | DIGCF_DEVICEINTERFACE); + if (FAILED(hr)) + return hr; + + for (DWORD index = 0; ; index++) + { + DeviceInfo deviceInfo; + if (!SetupDiEnumDeviceInfo(deviceInfoSet, index, &deviceInfo)) + { + DWORD dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_ITEMS) + { + return HRESULT_FROM_WIN32(dwError); + } + + break; + } + + auto device = std::make_shared(); + if (SUCCEEDED(deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_InstanceId, device->m_instanceId))) + { + ParseVidPid(device->m_instanceId.c_str(), L"VID_", device->m_vid); + ParseVidPid(device->m_instanceId.c_str(), L"PID_", device->m_pid); + + deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_IsPresent, device->m_isPresent); + deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_BusReportedDeviceDesc, device->m_productName); + deviceInfo.GetRegistryValue(deviceInfoSet, L"PortName", device->m_port); + + device->m_isCompositeDevice = IsCompositeDevice(device->m_instanceId.c_str()); + if (device->m_isCompositeDevice) + { + GetCompositeDevicePortLocation(deviceInfoSet, deviceInfo, device->m_location); + } + else + { + deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_LocationInfo, device->m_location); + } + + m_devices.push_back(std::move(device)); + } + } + + return S_OK; + } + + bool ParseVidPid(_In_z_ wchar_t const* buffer, wchar_t const* prefix, uint16_t& value) + { + wchar_t const* match = wcsstr(buffer, prefix); + if (match == nullptr) + { + return false; + } + + match += wcslen(prefix); + + wchar_t* endptr = nullptr; + value = static_cast(std::wcstoul(match, &endptr, 16)); + return endptr == match + 4; + } + + private: + HRESULT GetCompositeDevicePortLocation(HDEVINFO hDeviceInfoSet, DeviceInfo& deviceInfo, std::wstring& location) + { + DeviceInfoSet deviceInfoSet; + HRESULT hr = deviceInfoSet.Create(nullptr, nullptr); + if (FAILED(hr)) + { + return hr; + } + + std::wstring parentDeviceInstanceId; + hr = deviceInfo.GetProperty(hDeviceInfoSet, &DEVPKEY_Device_Parent, parentDeviceInstanceId); + if (FAILED(hr)) + { + return hr; + } + + DeviceInfo parentDeviceInfo; + hr = parentDeviceInfo.Open(deviceInfoSet, parentDeviceInstanceId.c_str()); + if (FAILED(hr)) + { + return hr; + } + + hr = parentDeviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_LocationInfo, location); + if (FAILED(hr)) + { + return hr; + } + + return S_OK; + } + + static bool IsCompositeDevice(LPCWSTR deviceInstanceId) + { + return wcsstr(deviceInstanceId, L"&MI_") != nullptr; + } + + private: + std::vector> m_devices; + }; + + class Locator + { + public: + struct Options + { + bool WaitForDevice = true; + bool FindRelatedDevices = true; + bool AutoReset = true; + uint32_t Retries = 0xFFFFFFFF; + uint32_t RetryIntervall = 100; + }; + + public: + Locator(IConsole* console = nullptr) : m_console(console) + { + } + + Locator(const Locator&) = delete; + Locator& operator=(const Locator& other) = delete; + Locator(Locator&&) = delete; + Locator& operator=(Locator&& other) = delete; + + public: + std::string FindPortForDevice(const std::string& deviceId, const Options* options = nullptr) + { + auto device = FindDevice(Encoding::ToUnicode(deviceId), options); + if (device == nullptr) + return std::string(); + + return Encoding::ToAnsi(device->m_port); + } + + std::wstring FindPortForDevice(const std::wstring& deviceId, const Options* options = nullptr) + { + auto device = FindDevice(deviceId, options); + if (device == nullptr) + return std::wstring(); + + return device->m_port; + } + + std::shared_ptr FindDevice(const std::string& deviceId, const Options* options = nullptr) + { + return FindDevice(Encoding::ToUnicode(deviceId), options); + } + + std::shared_ptr FindDevice(const std::wstring& deviceId, const Options* options = nullptr) + { + if (options == nullptr) + { + static Options defaults; + options = &defaults; + } + + uint32_t retries = options->Retries; + while (true) + { + m_enumerator.EnumerateDevices(true); + + auto devices = FindDevices(deviceId, options->FindRelatedDevices); + if (devices.size() == 0) + { + throw std::runtime_error("Device not found."); + } + + auto device = devices[0]; + if (device->m_isPresent) + { + PrintMessage(MessageLevel::Verbose, "Found device at port '%ws'", device->m_port.c_str()); + PrintDeviceProperties(device); + + HRESULT hr = device->WaitForPortAvailability(options->Retries, options->RetryIntervall); + if (FAILED(hr)) + { + PrintMessage(MessageLevel::Error, "Failed to open serial port: %ws", Win32::CreateMessageFromHResult(hr).c_str()); + throw std::runtime_error("Failed to open serial port."); + } + + if (device->IsBootloaderMode() || !options->AutoReset) + { + return device; + } + + PrintMessage(MessageLevel::Info, "Entering bootloader mode..."); + hr = device->Reset(); + if (FAILED(hr)) + { + PrintMessage(MessageLevel::Error, "Failed to reset device: %ws", Win32::CreateMessageFromHResult(hr).c_str()); + throw std::runtime_error("Failed to reset device."); + } + + retries = options->Retries; + + while (true) + { + m_enumerator.EnumerateDevices(true); + auto relatedDevices = FindRelatedDevices(devices); + if (relatedDevices.size() > 0) + { + auto relatedDevice = relatedDevices[0]; + if (relatedDevice->m_isPresent) + { + PrintMessage(MessageLevel::Verbose, "Found device at port '%ws'", relatedDevice->m_port.c_str()); + PrintDeviceProperties(relatedDevice); + + hr = relatedDevice->WaitForPortAvailability(options->Retries, options->RetryIntervall); + if (FAILED(hr)) + { + PrintMessage(MessageLevel::Error, "Failed to open serial port: %ws", Win32::CreateMessageFromHResult(hr).c_str()); + throw std::runtime_error("Failed to open serial port."); + } + + return relatedDevice; + } + } + + if (!options->WaitForDevice || retries == 0) + { + return nullptr; + } + + Sleep(options->RetryIntervall); + retries--; + } + } + + if (!options->WaitForDevice || retries == 0) + { + return nullptr; + } + + if (retries == options->Retries) + { + PrintMessage(MessageLevel::Info, "No device present, retrying every %ums...", options->RetryIntervall); + } + + Sleep(options->RetryIntervall); + retries--; + } + } + + void LocateAllDevices() + { + PrintMessage(MessageLevel::Verbose, "Enumerating devices..."); + m_enumerator.EnumerateDevices(true); + + for (auto& device : m_enumerator.GetDevices()) + { + PrintMessage(MessageLevel::Info, "Found device: %ws", device->m_instanceId.c_str()); + PrintDeviceProperties(device); + } + } + + private: + std::vector> FindDevices(const std::wstring& deviceId, bool findRelatedDevices = false) + { + uint32_t comPort = 0; + uint16_t vid = 0; + uint16_t pid = 0; + if (swscanf_s(ToUpper(deviceId).c_str(), L"COM%u", &comPort) == 1) + { + auto devices = FindDevicesByPort(deviceId); + if (devices.size() > 0) + { + // 1) Return plugged-in devices with exact COM port match. + return devices; + } + + auto unpluggedDevices = FindDevicesByPort(deviceId, true); + + if (findRelatedDevices) + { + auto relatedDevices = FindRelatedDevices(unpluggedDevices); + if (relatedDevices.size() > 0) + { + // 2) Return plugged-in devices related to the COM port. + return relatedDevices; + } + } + + // 3) Return unplugged devices with exact COM port match. + return unpluggedDevices; + } + else if (swscanf_s(ToUpper(deviceId).c_str(), L"USB:%hX:%hX", &vid, &pid) == 2) + { + auto devices = FindDevicesByUsbId(vid, pid); + if (devices.size() > 0) + { + // 1) Return plugged-in devices with exact USB VID:PID match. + return devices; + } + + auto unpluggedDevices = FindDevicesByUsbId(vid, pid, std::wstring(), true); + + if (findRelatedDevices) + { + // 2) Return plugged-in devices with related USB VID:PID and HUB location. + auto relatedDevices = FindRelatedDevices(unpluggedDevices); + if (relatedDevices.size() > 0) + { + return relatedDevices; + } + + // 3) Return plugged-in devices with related USB VID:PID (bootloader device never plugged-in). + relatedDevices = FindRelatedDevices(vid, pid); + if (relatedDevices.size() > 0) + { + return relatedDevices; + } + } + + // 4) Return unplugged devices with exact USB VID:PID match. + return unpluggedDevices; + } + else + { + throw std::runtime_error("Invalid device specifier."); + } + } + + std::vector> FindDevicesByPort(const std::wstring& port, bool includeUnpluggedDevices = false) + { + std::vector> devices; + + for (auto& device : m_enumerator.GetDevices()) + { + if ((device->m_isPresent || includeUnpluggedDevices) && + _wcsicmp(device->m_port.c_str(), port.c_str()) == 0) + { + devices.push_back(device); + } + } + + return devices; + } + + std::vector> FindDevicesByUsbId(uint16_t vid, uint16_t pid, const std::wstring& location = std::wstring(), bool includeUnpluggedDevices = false) + { + std::vector> devices; + + for (auto& device : m_enumerator.GetDevices()) + { + if ((device->m_isPresent || includeUnpluggedDevices) && + device->m_vid == vid && + device->m_pid == pid && + (location.empty() || device->m_location == location)) + { + devices.push_back(device); + } + } + + return devices; + } + + std::vector> FindRelatedDevices(const std::vector>& devices) + { + std::vector> relatedDevices; + + for (const auto& device : devices) + { + auto relatedPids = GetRelatedPids(device->m_pid, device->IsBootloaderMode()); + for (auto relatedPid : relatedPids) + { + auto list = FindDevicesByUsbId(device->m_vid, relatedPid, device->m_location); + relatedDevices.insert(relatedDevices.end(), list.begin(), list.end()); + } + } + + return relatedDevices; + } + + std::vector> FindRelatedDevices(uint16_t vid, uint16_t pid) + { + std::vector> relatedDevices; + + auto relatedPids = GetRelatedPids(pid, true); + for (auto relatedPid : relatedPids) + { + auto list = FindDevicesByUsbId(vid, relatedPid); + relatedDevices.insert(relatedDevices.end(), list.begin(), list.end()); + } + + return relatedDevices; + } + + static std::vector GetRelatedPids(uint16_t pid, bool isBootloaderMode) + { + // Arduino boards with a direct USB connection (such as Leonardo or Micro) + // have one USB serial port for the bootloader and one for the sketch. + // Bootloader and sketch devices have the same VID but different PIDs. + // To find related PIDs (bootloader vs. sketch device), we use the following heuristics: + // Arduino, Adafruit, etc.: + // - Bootloader PID: PIDxxxx with the MSB cleared + // - Sketch PID: PIDxxxx with the MSB set + // Others, who did not understand the above (Sparkfun): + // - Bootloader PID: PIDxxxx + // - Sketch PID: PIDxxxx + 1 + + std::vector relatedPids; + + if (isBootloaderMode) + { + relatedPids.push_back(pid | 0x8000); + relatedPids.push_back(pid + 1); + } + else + { + relatedPids.push_back(pid & 0x7FFF); + relatedPids.push_back(pid - 1); + } + + return relatedPids; + } + + private: + static std::wstring ToUpper(std::wstring text) + { + for (auto& ch : text) + { + ch = static_cast(std::towupper(ch)); + } + + return text; + } + + void PrintMessage(MessageLevel level, _In_z_ _Printf_format_string_ const char* format, ...) + { + if (m_console != nullptr) + { + va_list args; + va_start(args, format); + PrintMessage(level, format, args); + va_end(args); + } + } + + void PrintMessage(MessageLevel level, _In_z_ _Printf_format_string_ const char* format, va_list args) + { + if (m_console != nullptr) + { + va_list args2; + va_copy(args2, args); + size_t needed = vsnprintf(nullptr, 0, format, args2); + va_end(args2); + + std::string str; + if (needed > 0) + { + str.resize(needed + 1); + size_t used = vsnprintf(&str[0], needed + 1, format, args); + str.resize(used); + } + + m_console->WriteLine(level, str); + } + } + + void PrintDeviceProperties(const std::shared_ptr& device) + { + if (m_console != nullptr) + { + PrintMessage(MessageLevel::Debug, "- InstanceId: %ws", device->m_instanceId.c_str()); + PrintMessage(MessageLevel::Debug, "- Port: %ws", device->m_port.c_str()); + PrintMessage(MessageLevel::Debug, "- Location: %ws", device->m_location.c_str()); + PrintMessage(MessageLevel::Debug, "- ProductName: %ws", device->m_productName.c_str()); + PrintMessage(MessageLevel::Debug, "- IsPresent: %s", device->m_isPresent ? "true" : "false"); + PrintMessage(MessageLevel::Debug, "- IsCompositeDevice: %s", device->m_isCompositeDevice ? "true" : "false"); + } + } + + private: + IConsole* m_console = nullptr; + Enumerator m_enumerator; + }; +} diff --git a/ser_win32.c b/ser_win32.c index fcaa55e6..50c18f01 100644 --- a/ser_win32.c +++ b/ser_win32.c @@ -40,6 +40,10 @@ #include "avrdude.h" #include "libavrdude.h" +#ifdef _MSC_VER +#include "msvc/usb_com_helper.h" +#endif + long serial_recv_timeout = 5000; /* ms */ #define W32SERBUFSIZE 1024 @@ -238,6 +242,13 @@ static int ser_open(char * port, union pinfo pinfo, union filedescriptor *fdp) #endif } +#ifdef _MSC_VER + if (win32_find_usb_com_port(port, &newname, true, true, true) >= 0) + { + port = newname; + } +#endif + if (strncasecmp(port, "com", strlen("com")) == 0) { // prepend "\\\\.\\" to name, required for port # >= 10