diff --git a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp new file mode 100644 index 00000000..189f056f --- /dev/null +++ b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "src/Targets/RiscV/DebugModule/DebugModule.hpp" +#include "src/Targets/RiscV/TargetParameters.hpp" + +namespace DebugToolDrivers::TargetInterfaces::RiscV +{ + class RiscVDebugInterface + { + public: + RiscVDebugInterface() = default; + virtual ~RiscVDebugInterface() = default; + + RiscVDebugInterface(const RiscVDebugInterface& other) = default; + RiscVDebugInterface(RiscVDebugInterface&& other) = default; + + RiscVDebugInterface& operator = (const RiscVDebugInterface& other) = default; + RiscVDebugInterface& operator = (RiscVDebugInterface&& other) = default; + + /** + * Should prepare for and then activate the physical interface between the debug tool and the RISC-V target. + * + * Should throw an exception if activation fails. The error will be considered fatal, and result in a shutdown. + * + * Unless otherwise stated, it can be assumed that this function will be called (and must succeed) + * before any of the other functions below this point are called. In other words, we can assume that the + * interface has been activated in the implementations of any of the functions below this point. + * + * @param targetParameters + * Parameters for the RISC-V target. These can be ignored if a particular implementation does not require + * any target parameters for activation. + */ + virtual void activate(const Targets::RiscV::TargetParameters& targetParameters) = 0; + + /** + * Should deactivate the physical interface between the debug tool and the RISC-V target. + * + * CAUTION: This function **CAN** be called before activate(), or in instances where activate() failed (threw + * an exception). Implementations must accommodate this. + */ + virtual void deactivate() = 0; + + /** + * Should retrieve the RISC-V target ID in string form. + * + * @return + * The target ID, in the form of a string. + */ + virtual std::string getDeviceId() = 0; + + /** + * Should read the value of a debug module register. + * + * @param address + * The address of the debug module register to read. + * + * @return + * The register value. + */ + virtual Targets::RiscV::DebugModule::RegisterValue readDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address + ) = 0; + + /** + * Should write a value to a debug module register. + * + * @param address + * The address of the debug module to update. + * + * @param value + * The value to write. + */ + virtual void writeDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address, + Targets::RiscV::DebugModule::RegisterValue value + ) = 0; + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/AttachTarget.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/AttachTarget.hpp new file mode 100644 index 00000000..d513636a --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Control/AttachTarget.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 AttachTarget: public Command> + { + public: + AttachTarget() + : Command(0x0d) + { + this->payload = { + 0x02 + }; + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/DebugModuleInterfaceOperation.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/DebugModuleInterfaceOperation.hpp new file mode 100644 index 00000000..8471620e --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/DebugModuleInterfaceOperation.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp" +#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/DebugModuleInterfaceOperationResponse.hpp" + +#include "src/Targets/RiscV/DebugModule/DebugModule.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands +{ + class DebugModuleInterfaceOperation: public Command> + { + public: + using ExpectedResponseType = Responses::DebugModuleInterfaceOperationResponse; + + DebugModuleInterfaceOperation( + Targets::RiscV::DebugModule::DmiOperation operation, + Targets::RiscV::DebugModule::RegisterAddress address, + std::optional value = std::nullopt + ) + : Command(0x08) + { + if (!value.has_value()) { + value = 0x00; + } + + this->payload = { + address, + static_cast(*value >> 24), + static_cast(*value >> 16), + static_cast(*value >> 8), + static_cast(*value), + static_cast(operation), + }; + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/SetClockSpeed.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/SetClockSpeed.hpp new file mode 100644 index 00000000..54616ad3 --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/SetClockSpeed.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands +{ + class SetClockSpeed: public Command> + { + public: + SetClockSpeed(std::uint8_t targetGroupId, std::uint8_t speedId) + : Command(0x0c) + { + this->payload = { + targetGroupId, + speedId, + }; + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/DebugModuleInterfaceOperationResponse.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/DebugModuleInterfaceOperationResponse.hpp new file mode 100644 index 00000000..178d5d63 --- /dev/null +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/DebugModuleInterfaceOperationResponse.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "src/Targets/RiscV/DebugModule/DebugModule.hpp" +#include "src/Helpers/BiMap.hpp" +#include "src/Services/StringService.hpp" + +#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" + +namespace DebugToolDrivers::Wch::Protocols::WchLink::Responses +{ + class DebugModuleInterfaceOperationResponse + { + public: + Targets::RiscV::DebugModule::DmiOperationStatus operationStatus; + Targets::RiscV::DebugModule::RegisterAddress address; + Targets::RiscV::DebugModule::RegisterValue value; + + explicit DebugModuleInterfaceOperationResponse(const std::vector& payload) + { + if (payload.size() != 6) { + throw Exceptions::DeviceCommunicationFailure( + "Unexpected response payload size for DMI operation command" + ); + } + + const auto status = payload[5]; + if ( + status != static_cast(Targets::RiscV::DebugModule::DmiOperationStatus::SUCCESS) + && status != static_cast(Targets::RiscV::DebugModule::DmiOperationStatus::FAILED) + && status != static_cast(Targets::RiscV::DebugModule::DmiOperationStatus::BUSY) + ) { + throw Exceptions::DeviceCommunicationFailure( + "Unknown DMI operation status returned: 0x" + Services::StringService::toHex(status) + ); + } + + this->operationStatus = static_cast(status); + this->address = payload[0]; + this->value = static_cast( + (payload[1] << 24) | (payload[2] << 16) | (payload[3] << 8) | (payload[4]) + ); + } + }; +} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp index d4ff44e5..aee6cc1e 100644 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp @@ -1,10 +1,18 @@ #include "WchLinkInterface.hpp" -#include - -#include "src/Helpers/BiMap.hpp" +#include #include "Commands/Control/GetDeviceInfo.hpp" +#include "Commands/Control/AttachTarget.hpp" +#include "Commands/SetClockSpeed.hpp" +#include "Commands/DebugModuleInterfaceOperation.hpp" + +#include "src/Helpers/BiMap.hpp" +#include "src/Services/StringService.hpp" + +#include "src/Logger/Logger.hpp" + +#include "src/Targets/RiscV/DebugModule/Registers/ControlRegister.hpp" #include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" @@ -12,14 +20,14 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink { using namespace Exceptions; + using Targets::RiscV::DebugModule::DmiOperation; + WchLinkInterface::WchLinkInterface(Usb::UsbInterface& usbInterface) : usbInterface(usbInterface) {} DeviceInfo WchLinkInterface::getDeviceInfo() { - const auto response = this->sendCommandAndWaitForResponse( - Commands::Control::GetDeviceInfo() - ); + const auto response = this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo()); if (response.payload.size() < 3) { throw Exceptions::DeviceCommunicationFailure("Cannot construct DeviceInfo response - invalid payload"); @@ -28,6 +36,7 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink static const auto variantsById = BiMap({ {0x01, WchLinkVariant::LINK_CH549}, {0x02, WchLinkVariant::LINK_E_CH32V307}, + {0x12, WchLinkVariant::LINK_E_CH32V307}, {0x03, WchLinkVariant::LINK_S_CH32V203}, }); @@ -38,4 +47,79 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink : std::nullopt ); } + + void WchLinkInterface::activate(const Targets::RiscV::TargetParameters& targetParameters) { + this->setClockSpeed(WchLinkTargetClockSpeed::CLK_6000_KHZ); + + const auto response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget()); + + if (response.payload.size() != 5) { + throw Exceptions::DeviceCommunicationFailure("Unexpected response payload size for AttachTarget command"); + } + + this->cachedTargetId = static_cast( + (response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8) + | (response.payload[4]) + ); + this->cachedTargetGroupId = response.payload[0]; + } + + void WchLinkInterface::deactivate() { + // TODO: implement this + } + + std::string WchLinkInterface::getDeviceId() { + return "0x" + Services::StringService::toHex(this->cachedTargetId.value()); + } + + Targets::RiscV::DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address + ) { + using Targets::RiscV::DebugModule::DmiOperationStatus ; + + const auto response = this->sendCommandAndWaitForResponse( + Commands::DebugModuleInterfaceOperation(DmiOperation::READ, address) + ); + + if (response.operationStatus != DmiOperationStatus::SUCCESS) { + throw Exceptions::DeviceCommunicationFailure("DMI operation failed"); + } + + return response.value; + } + + void WchLinkInterface::writeDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address, + Targets::RiscV::DebugModule::RegisterValue value + ) { + using Targets::RiscV::DebugModule::DmiOperationStatus ; + + const auto response = this->sendCommandAndWaitForResponse( + Commands::DebugModuleInterfaceOperation(DmiOperation::WRITE, address, value) + ); + + if (response.operationStatus != DmiOperationStatus::SUCCESS) { + throw Exceptions::DeviceCommunicationFailure("DMI operation failed"); + } + } + + void WchLinkInterface::setClockSpeed(WchLinkTargetClockSpeed speed) { + const auto speedIdsBySpeed = BiMap({ + {WchLinkTargetClockSpeed::CLK_6000_KHZ, 0x01}, + {WchLinkTargetClockSpeed::CLK_4000_KHZ, 0x02}, + {WchLinkTargetClockSpeed::CLK_400_KHZ, 0x03}, + }); + + const auto response = this->sendCommandAndWaitForResponse( + Commands::SetClockSpeed(this->cachedTargetGroupId.value_or(0x01), speedIdsBySpeed.at(speed)) + ); + + if (response.payload.size() != 1) { + throw Exceptions::DeviceCommunicationFailure("Unexpected response payload size for SetClockSpeed command"); + } + + if (response.payload[0] != 0x01) { + throw Exceptions::DeviceCommunicationFailure("Unexpected response for SetClockSpeed command"); + } + } } diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp index e4ccea43..55d75b5d 100644 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp @@ -4,29 +4,67 @@ #include #include +#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp" #include "src/DebugToolDrivers/USB/UsbInterface.hpp" +#include "src/DebugToolDrivers/WCH/WchGeneric.hpp" #include "Commands/Command.hpp" +#include "src/Targets/RiscV/DebugModule/DebugModule.hpp" #include "src/DebugToolDrivers/WCH/DeviceInfo.hpp" #include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" namespace DebugToolDrivers::Wch::Protocols::WchLink { - class WchLinkInterface + /** + * The WchLinkInterface implements the WCH-Link protocol. + */ + class WchLinkInterface: public TargetInterfaces::RiscV::RiscVDebugInterface { public: explicit WchLinkInterface(Usb::UsbInterface& usbInterface); DeviceInfo getDeviceInfo(); + void activate(const Targets::RiscV::TargetParameters& targetParameters) override; + + void deactivate() override; + + std::string getDeviceId() override; + + Targets::RiscV::DebugModule::RegisterValue readDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address + ) override; + + void writeDebugModuleRegister( + Targets::RiscV::DebugModule::RegisterAddress address, + Targets::RiscV::DebugModule::RegisterValue value + ) override; + private: static constexpr std::uint8_t USB_ENDPOINT_IN = 0x81; static constexpr std::uint8_t USB_ENDPOINT_OUT = 0x01; Usb::UsbInterface& usbInterface; + /** + * The 'target activation' command returns a payload of 5 bytes. + * + * The last 4 bytes hold the WCH RISC-V target ID. Given that the 'target activation' command appears to be + * the only way to obtain the target ID, we cache it via WchLinkInterface::cachedTargetId and return the + * cached value in WchLinkInterface::getTargetId(). + * + * As for the first byte in the payload, I'm not really sure what it is. It appears to be some kind of + * identifier for groups of WCH RISC-V targets. It's unclear. All I know is that it has some significance, as + * it's expected in the payload of some other commands, such as the command to set clock speed. For this + * reason, we have to keep hold of it via WchLinkInterface::cachedTargetGroupId. + */ + std::optional cachedTargetId; + std::optional cachedTargetGroupId; + + void setClockSpeed(WchLinkTargetClockSpeed speed); + template auto sendCommandAndWaitForResponse(const CommandType& command) { this->usbInterface.writeBulk(WchLinkInterface::USB_ENDPOINT_OUT, command.getRawCommand()); diff --git a/src/DebugToolDrivers/WCH/WchGeneric.hpp b/src/DebugToolDrivers/WCH/WchGeneric.hpp index bce86a0c..6a07d970 100644 --- a/src/DebugToolDrivers/WCH/WchGeneric.hpp +++ b/src/DebugToolDrivers/WCH/WchGeneric.hpp @@ -4,6 +4,8 @@ namespace DebugToolDrivers::Wch { + using WchTargetId = std::uint32_t; + enum class WchLinkVariant: std::uint8_t { LINK_CH549, @@ -11,4 +13,11 @@ namespace DebugToolDrivers::Wch LINK_S_CH32V203, UNKNOWN, }; + + enum class WchLinkTargetClockSpeed: std::uint8_t + { + CLK_400_KHZ, + CLK_4000_KHZ, + CLK_6000_KHZ, + }; } diff --git a/src/Targets/RiscV/DebugModule/DebugModule.hpp b/src/Targets/RiscV/DebugModule/DebugModule.hpp new file mode 100644 index 00000000..47c4b9fb --- /dev/null +++ b/src/Targets/RiscV/DebugModule/DebugModule.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +namespace Targets::RiscV::DebugModule +{ + using RegisterAddress = std::uint8_t; + using RegisterValue = std::uint32_t; + using HartIndex = std::uint32_t; + + enum class DmiOperation: std::uint8_t + { + IGNORE = 0x00, + READ = 0x01, + WRITE = 0x02, + }; + + enum class DmiOperationStatus: std::uint8_t + { + SUCCESS = 0x00, + FAILED = 0x02, + BUSY = 0x03, + }; +} diff --git a/src/Targets/RiscV/TargetParameters.hpp b/src/Targets/RiscV/TargetParameters.hpp new file mode 100644 index 00000000..e93c5ab9 --- /dev/null +++ b/src/Targets/RiscV/TargetParameters.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "src/Targets/TargetMemory.hpp" + +namespace Targets::RiscV +{ + struct TargetParameters + {}; +}