diff --git a/src/DebugToolDrivers/CMakeLists.txt b/src/DebugToolDrivers/CMakeLists.txt index aadfd65b..6ab87fe6 100755 --- a/src/DebugToolDrivers/CMakeLists.txt +++ b/src/DebugToolDrivers/CMakeLists.txt @@ -33,4 +33,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Microchip/XplainedNano/XplainedNano.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Microchip/CuriosityNano/CuriosityNano.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Microchip/JtagIce3/JtagIce3.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/WCH/Protocols/WchLink/WchLinkInterface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkBase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkE/WchLinkE.cpp ) diff --git a/src/DebugToolDrivers/DebugTools.hpp b/src/DebugToolDrivers/DebugTools.hpp index 99a09f6a..7e2bf049 100644 --- a/src/DebugToolDrivers/DebugTools.hpp +++ b/src/DebugToolDrivers/DebugTools.hpp @@ -11,3 +11,5 @@ #include "src/DebugToolDrivers/Microchip/XplainedNano/XplainedNano.hpp" #include "src/DebugToolDrivers/Microchip/CuriosityNano/CuriosityNano.hpp" #include "src/DebugToolDrivers/Microchip/JtagIce3/JtagIce3.hpp" + +#include "src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.hpp" diff --git a/src/DebugToolDrivers/WCH/DeviceInfo.hpp b/src/DebugToolDrivers/WCH/DeviceInfo.hpp new file mode 100644 index 00000000..d2ebc8c0 --- /dev/null +++ b/src/DebugToolDrivers/WCH/DeviceInfo.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "src/DebugToolDrivers/WCH/WchFirmwareVersion.hpp" +#include "src/DebugToolDrivers/WCH/WchGeneric.hpp" + +namespace DebugToolDrivers::Wch +{ + class DeviceInfo + { + public: + WchFirmwareVersion firmwareVersion; + std::optional variant; + + explicit DeviceInfo(WchFirmwareVersion firmwareVersion, std::optional variant) + : firmwareVersion(firmwareVersion) + , variant(variant) + {} + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp new file mode 100644 index 00000000..f78692f8 --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/Response.hpp" + + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands +{ + template > + class Command + { + static_assert( + std::is_same_v, + "Invalid payload container type" + ); + + public: + using ExpectedResponseType = Responses::Response; + + std::uint8_t commandId; + PayloadContainerType payload; + + explicit Command(std::uint8_t commandId) + : commandId(commandId) + {}; + + virtual ~Command() = default; + + Command(const Command& other) = default; + Command(Command&& other) noexcept = default; + + Command& operator = (const Command& other) = default; + Command& operator = (Command&& other) noexcept = default; + + [[nodiscard]] auto getRawCommand() const { + assert(this->payload.size() <= 256); + + auto rawCommand = std::vector(3 + this->payload.size()); + + rawCommand[0] = 0x81; + rawCommand[1] = this->commandId; + rawCommand[2] = static_cast(this->payload.size()); + + if (!this->payload.empty()) { + std::copy(this->payload.begin(), this->payload.end(), rawCommand.begin() + 3); + } + + return rawCommand; + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/GetDeviceInfo.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/GetDeviceInfo.hpp new file mode 100644 index 00000000..9b5a12af --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/GetDeviceInfo.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands::Control +{ + class GetDeviceInfo: public Command> + { + public: + GetDeviceInfo() + : Command(0x0d) + { + this->payload = { + 0x01 + }; + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/Response.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/Response.hpp new file mode 100644 index 00000000..c9d412fc --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/Response.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Responses +{ + class Response + { + public: + std::vector payload; + + explicit Response(const std::vector& payload) + : payload(payload) + {} + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp new file mode 100644 index 00000000..d4ff44e5 --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp @@ -0,0 +1,41 @@ +#include "WchLinkInterface.hpp" + +#include + +#include "src/Helpers/BiMap.hpp" + +#include "Commands/Control/GetDeviceInfo.hpp" + +#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink +{ + using namespace Exceptions; + + WchLinkInterface::WchLinkInterface(Usb::UsbInterface& usbInterface) + : usbInterface(usbInterface) + {} + + DeviceInfo WchLinkInterface::getDeviceInfo() { + const auto response = this->sendCommandAndWaitForResponse( + Commands::Control::GetDeviceInfo() + ); + + if (response.payload.size() < 3) { + throw Exceptions::DeviceCommunicationFailure("Cannot construct DeviceInfo response - invalid payload"); + } + + static const auto variantsById = BiMap({ + {0x01, WchLinkVariant::LINK_CH549}, + {0x02, WchLinkVariant::LINK_E_CH32V307}, + {0x03, WchLinkVariant::LINK_S_CH32V203}, + }); + + return DeviceInfo( + WchFirmwareVersion(response.payload[0], response.payload[1]), + response.payload.size() >= 4 + ? std::optional(variantsById.valueAt(response.payload[2]).value_or(WchLinkVariant::UNKNOWN)) + : std::nullopt + ); + } +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp new file mode 100644 index 00000000..e4ccea43 --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#include "src/DebugToolDrivers/USB/UsbInterface.hpp" + +#include "Commands/Command.hpp" + +#include "src/DebugToolDrivers/WCH/DeviceInfo.hpp" + +#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink +{ + class WchLinkInterface + { + public: + explicit WchLinkInterface(Usb::UsbInterface& usbInterface); + + DeviceInfo getDeviceInfo(); + + private: + static constexpr std::uint8_t USB_ENDPOINT_IN = 0x81; + static constexpr std::uint8_t USB_ENDPOINT_OUT = 0x01; + + Usb::UsbInterface& usbInterface; + + template + auto sendCommandAndWaitForResponse(const CommandType& command) { + this->usbInterface.writeBulk(WchLinkInterface::USB_ENDPOINT_OUT, command.getRawCommand()); + + const auto rawResponse = this->usbInterface.readBulk(WchLinkInterface::USB_ENDPOINT_IN); + + if (rawResponse.size() < 3) { + throw Exceptions::DeviceCommunicationFailure("Invalid response size from device"); + } + + // The first byte of the response should be 0x82 (for success) or 0x81 (for failure) + if ((rawResponse[0] != 0x81 && rawResponse[0] != 0x82)) { + throw Exceptions::DeviceCommunicationFailure("Invalid response code from device"); + } + + if (rawResponse[0] == 0x81) { + // TODO: Create ErrorResponse exception class and throw an instance of it here. + throw Exceptions::DeviceCommunicationFailure("Error response"); + } + + if (rawResponse[1] != command.commandId) { + throw Exceptions::DeviceCommunicationFailure("Missing/invalid command ID in response from device"); + } + + if ((rawResponse.size() - 3) != rawResponse[2]) { + throw Exceptions::DeviceCommunicationFailure("Actual response payload size mismatch"); + } + + return typename CommandType::ExpectedResponseType( + std::vector(rawResponse.begin() + 3, rawResponse.end()) + ); + } + }; +} diff --git a/src/DebugToolDrivers/WCH/WchFirmwareVersion.hpp b/src/DebugToolDrivers/WCH/WchFirmwareVersion.hpp new file mode 100644 index 00000000..c8e1935f --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchFirmwareVersion.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace DebugToolDrivers::Wch +{ + class WchFirmwareVersion + { + public: + std::uint8_t major = 0; + std::uint8_t minor = 0; + + WchFirmwareVersion() = default; + WchFirmwareVersion(std::uint8_t major, std::uint8_t minor) + : major{major} + , minor{minor} + {} + + std::string toString() const { + return std::to_string(this->major) + "." + std::to_string(this->minor); + } + + bool operator == (const WchFirmwareVersion& other) const { + return this->major == other.major && this->minor == other.minor; + } + + bool operator != (const WchFirmwareVersion& other) const { + return !(*this == other); + } + + bool operator < (const WchFirmwareVersion& other) const { + return this->major < other.major || (this->major == other.major && this->minor < other.minor); + } + + bool operator > (const WchFirmwareVersion& other) const { + return other < *this; + } + + bool operator <= (const WchFirmwareVersion& other) const { + return !(other < *this); + } + + bool operator >= (const WchFirmwareVersion& other) const { + return !(*this < other); + } + }; +} diff --git a/src/DebugToolDrivers/WCH/WchGeneric.hpp b/src/DebugToolDrivers/WCH/WchGeneric.hpp new file mode 100644 index 00000000..bce86a0c --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchGeneric.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace DebugToolDrivers::Wch +{ + enum class WchLinkVariant: std::uint8_t + { + LINK_CH549, + LINK_E_CH32V307, + LINK_S_CH32V203, + UNKNOWN, + }; +} diff --git a/src/DebugToolDrivers/WCH/WchLinkBase.cpp b/src/DebugToolDrivers/WCH/WchLinkBase.cpp new file mode 100644 index 00000000..6159761d --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkBase.cpp @@ -0,0 +1,69 @@ +#include "WchLinkBase.hpp" + +#include "Protocols/WchLink/Commands/Control/GetDeviceInfo.hpp" + +#include "src/TargetController/Exceptions/DeviceFailure.hpp" +#include "src/TargetController/Exceptions/DeviceInitializationFailure.hpp" + +#include "src/Logger/Logger.hpp" + +namespace DebugToolDrivers::Wch +{ + using Exceptions::DeviceFailure; + using Exceptions::DeviceInitializationFailure; + + WchLinkBase::WchLinkBase( + WchLinkVariant variant, + std::uint16_t vendorId, + std::uint16_t productId, + std::uint8_t wchLinkUsbInterfaceNumber + ) + : UsbDevice(vendorId, productId) + , variant(variant) + , wchLinkUsbInterfaceNumber(wchLinkUsbInterfaceNumber) + {} + + void WchLinkBase::init() { + UsbDevice::init(); + + this->detachKernelDriverFromInterface(this->wchLinkUsbInterfaceNumber); + + this->wchLinkUsbInterface = std::make_unique( + this->wchLinkUsbInterfaceNumber, + this->libusbDeviceHandle.get() + ); + + this->wchLinkUsbInterface->init(); + + this->wchLinkInterface = std::make_unique( + *(this->wchLinkUsbInterface.get()) + ); + + if (this->getDeviceInfo().variant != this->variant) { + throw DeviceInitializationFailure( + "WCH-Link variant mismatch - device returned variant ID that doesn't match the " + this->getName() + + " variant ID" + ); + } + + this->setInitialised(true); + } + + void WchLinkBase::close() { + if (this->wchLinkUsbInterface) { + this->wchLinkUsbInterface->close(); + } + } + + std::string WchLinkBase::getSerialNumber() { + return UsbDevice::getSerialNumber(); + } + + const DeviceInfo& WchLinkBase::getDeviceInfo() const { + if (!this->cachedDeviceInfo.has_value()) { + this->cachedDeviceInfo = this->wchLinkInterface->getDeviceInfo(); + } + + return *(this->cachedDeviceInfo); + } +} diff --git a/src/DebugToolDrivers/WCH/WchLinkBase.hpp b/src/DebugToolDrivers/WCH/WchLinkBase.hpp new file mode 100644 index 00000000..cf620916 --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkBase.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include "src/DebugToolDrivers/DebugTool.hpp" +#include "src/DebugToolDrivers/USB/UsbDevice.hpp" + +#include "Protocols/WchLink/WchLinkInterface.hpp" + +#include "WchGeneric.hpp" +#include "DeviceInfo.hpp" + +namespace DebugToolDrivers::Wch +{ + class WchLinkBase: public DebugTool, public Usb::UsbDevice + { + public: + WchLinkBase( + WchLinkVariant variant, + std::uint16_t vendorId, + std::uint16_t productId, + std::uint8_t wchLinkUsbInterfaceNumber + ); + + void init() override; + + void close() override; + + std::string getSerialNumber() override; + + protected: + WchLinkVariant variant; + + std::uint8_t wchLinkUsbInterfaceNumber; + std::unique_ptr wchLinkUsbInterface = nullptr; + std::unique_ptr wchLinkInterface = nullptr; + + mutable std::optional cachedDeviceInfo = std::nullopt; + + const DeviceInfo& getDeviceInfo() const; + }; +} diff --git a/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.cpp b/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.cpp new file mode 100644 index 00000000..7df8c0a8 --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.cpp @@ -0,0 +1,13 @@ +#include "WchLinkE.hpp" + +namespace DebugToolDrivers::Wch +{ + WchLinkE::WchLinkE() + : WchLinkBase( + WchLinkVariant::LINK_E_CH32V307, + WchLinkE::USB_VENDOR_ID, + WchLinkE::USB_PRODUCT_ID, + WchLinkE::WCH_LINK_INTERFACE_NUMBER + ) + {} +} diff --git a/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.hpp b/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.hpp new file mode 100644 index 00000000..7f1eb46d --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkE/WchLinkE.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "src/DebugToolDrivers/WCH/WchLinkBase.hpp" + +namespace DebugToolDrivers::Wch +{ + /** + * The WCH-LinkE debug tool is a variant of the WCH-Link. + * + * USB: + * Vendor ID: 0x1a86 (6790) + * Product ID: 0x8010 (32784) + */ + class WchLinkE: public WchLinkBase + { + public: + static const inline std::uint16_t USB_VENDOR_ID = 0x1a86; + static const inline std::uint16_t USB_PRODUCT_ID = 0x8010; + static const inline std::uint8_t WCH_LINK_INTERFACE_NUMBER = 0; + + WchLinkE(); + + std::string getName() override { + return "WCH-LinkE"; + } + }; +} diff --git a/src/TargetController/TargetControllerComponent.cpp b/src/TargetController/TargetControllerComponent.cpp index 6949cfdf..09ee9650 100644 --- a/src/TargetController/TargetControllerComponent.cpp +++ b/src/TargetController/TargetControllerComponent.cpp @@ -362,6 +362,12 @@ namespace TargetController return std::make_unique(); } }, + { + "wch-link-e", + [] { + return std::make_unique(); + } + }, }; }