diff --git a/src/DebugToolDrivers/CMakeLists.txt b/src/DebugToolDrivers/CMakeLists.txt index f47deb99..497e7f7f 100755 --- a/src/DebugToolDrivers/CMakeLists.txt +++ b/src/DebugToolDrivers/CMakeLists.txt @@ -45,7 +45,7 @@ target_sources( # WCH debug tools and interface implementations ${CMAKE_CURRENT_SOURCE_DIR}/WCH/Protocols/WchLink/WchLinkInterface.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/WCH/Protocols/WchLink/WchLinkProgrammingInterface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkDebugInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkToolConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkE/WchLinkE.cpp diff --git a/src/DebugToolDrivers/DebugTool.hpp b/src/DebugToolDrivers/DebugTool.hpp index bd202a37..575e0ec5 100644 --- a/src/DebugToolDrivers/DebugTool.hpp +++ b/src/DebugToolDrivers/DebugTool.hpp @@ -8,8 +8,6 @@ #include "src/Targets/Microchip/AVR8/Avr8TargetConfig.hpp" #include "TargetInterfaces/RiscV/RiscVDebugInterface.hpp" -#include "TargetInterfaces/RiscV/RiscVProgramInterface.hpp" -#include "TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp" #include "src/Targets/RiscV/TargetDescriptionFile.hpp" #include "src/Targets/RiscV/RiscVTargetConfig.hpp" @@ -122,45 +120,4 @@ public: ) { return nullptr; } - - /** - * Some debug tools are unable to program RISC-V targets via the RISC-V debug interface. Such tools must provide - * an implementation of the RiscVProgramInterface, which will allow them to implement flash memory writing as a - * separate function, independent of the debug interface. - * - * The RISC-V target driver will forward all flash memory writes to the RiscVProgramInterface returned by this - * member function. If nullptr is returned, the driver will fall back to the RiscVDebugInterface for flash memory - * writes. - * - * Note: the caller of this function will not manage the lifetime of the returned instance. - * - * @return - */ - virtual DebugToolDrivers::TargetInterfaces::RiscV::RiscVProgramInterface* getRiscVProgramInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) { - return nullptr; - } - - /** - * The RISC-V debug spec does not define a target ID. But vendors typically assign each model with an ID and - * provide a means to extract it from the connected target, via the debug tool. - * - * For example, WCH debug tools return the target ID in response to the target activation command. For more, see - * the implementation of the WCH-Link protocol. - * - * Bloom uses the target ID for verification purposes. We simply compare it to the one we have in the TDF and shout - * if they don't match. - * - * Note: the caller of this function will not manage the lifetime of the returned instance. - * - * @return - */ - virtual DebugToolDrivers::TargetInterfaces::RiscV::RiscVIdentificationInterface* getRiscVIdentificationInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) { - return nullptr; - } }; diff --git a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp index 77d736a8..41e28c77 100644 --- a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp +++ b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.cpp @@ -77,13 +77,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec , targetConfig(targetConfig) {} - void DebugTranslator::init() { - // No pre-activation initialisation required. - } - void DebugTranslator::activate() { - this->dtmInterface.activate(); - this->debugModuleDescriptor.hartIndices = this->discoverHartIndices(); if (this->debugModuleDescriptor.hartIndices.empty()) { throw Exceptions::TargetOperationFailure{"Failed to discover any RISC-V harts"}; @@ -122,7 +116,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec if (!this->debugModuleDescriptor.triggerDescriptorsByIndex.empty()) { // Clear any left-over triggers from the previous debug session - this->clearAllHardwareBreakpoints(); + this->clearAllTriggerBreakpoints(); } this->initDebugControlStatusRegister(); @@ -177,7 +171,6 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec void DebugTranslator::deactivate() { this->disableDebugModule(); - this->dtmInterface.deactivate(); } TargetExecutionState DebugTranslator::getExecutionState() { @@ -305,19 +298,11 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec this->initDebugControlStatusRegister(); } - void DebugTranslator::setSoftwareBreakpoint(TargetMemoryAddress address) { - throw Exceptions::Exception{"SW breakpoints not supported"}; - } - - void DebugTranslator::clearSoftwareBreakpoint(TargetMemoryAddress address) { - throw Exceptions::Exception{"SW breakpoints not supported"}; - } - - std::uint16_t DebugTranslator::getHardwareBreakpointCount() { + std::uint16_t DebugTranslator::getTriggerCount() const { return static_cast(this->debugModuleDescriptor.triggerDescriptorsByIndex.size()); } - void DebugTranslator::setHardwareBreakpoint(TargetMemoryAddress address) { + void DebugTranslator::insertTriggerBreakpoint(TargetMemoryAddress address) { using TriggerModule::TriggerType; const auto triggerDescriptorOpt = this->getAvailableTrigger(); @@ -361,7 +346,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec throw Exceptions::Exception{"Unsupported trigger"}; } - void DebugTranslator::clearHardwareBreakpoint(TargetMemoryAddress address) { + void DebugTranslator::clearTriggerBreakpoint(TargetMemoryAddress address) { const auto triggerIndexIt = this->triggerIndicesByBreakpointAddress.find(address); if (triggerIndexIt == this->triggerIndicesByBreakpointAddress.end()) { throw Exceptions::Exception{"Unknown hardware breakpoint"}; @@ -374,7 +359,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec this->allocatedTriggerIndices.erase(triggerDescriptor.index); } - void DebugTranslator::clearAllHardwareBreakpoints() { + void DebugTranslator::clearAllTriggerBreakpoints() { // To ensure that any untracked breakpoints are cleared, we clear all triggers on the target. for (const auto& [triggerIndex, triggerDescriptor] : this->debugModuleDescriptor.triggerDescriptorsByIndex) { this->clearTrigger(triggerDescriptor); diff --git a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp index 58c4c0de..594c301c 100644 --- a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp +++ b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp @@ -10,16 +10,17 @@ #include #include -#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp" - #include "DebugTransportModuleInterface.hpp" #include "DebugTranslatorConfig.hpp" #include "DebugModuleDescriptor.hpp" +#include "src/Targets/TargetMemory.hpp" +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" +#include "src/Targets/TargetState.hpp" #include "src/Targets/RiscV/TargetDescriptionFile.hpp" #include "src/Targets/RiscV/RiscVTargetConfig.hpp" #include "src/Targets/RiscV/Opcodes/Opcode.hpp" -#include "src/Targets/TargetMemory.hpp" #include "Common.hpp" #include "Registers/CpuRegisterNumbers.hpp" @@ -42,7 +43,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec /** * Implementation of a RISC-V debug translator */ - class DebugTranslator: public ::DebugToolDrivers::TargetInterfaces::RiscV::RiscVDebugInterface + class DebugTranslator { public: DebugTranslator( @@ -54,29 +55,25 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec virtual ~DebugTranslator() = default; - void init() override; - void activate() override; - void deactivate() override; + void activate(); + void deactivate(); - Targets::TargetExecutionState getExecutionState() override; + Targets::TargetExecutionState getExecutionState(); - void stop() override; - void run() override; - void step() override; - void reset() override; + void stop(); + void run(); + void step(); + void reset(); - void setSoftwareBreakpoint(Targets::TargetMemoryAddress address) override; - void clearSoftwareBreakpoint(Targets::TargetMemoryAddress address) override; - - std::uint16_t getHardwareBreakpointCount() override; - void setHardwareBreakpoint(Targets::TargetMemoryAddress address) override; - void clearHardwareBreakpoint(Targets::TargetMemoryAddress address) override; - void clearAllHardwareBreakpoints() override; + std::uint16_t getTriggerCount() const; + void insertTriggerBreakpoint(Targets::TargetMemoryAddress address); + void clearTriggerBreakpoint(Targets::TargetMemoryAddress address); + void clearAllTriggerBreakpoints(); Targets::TargetRegisterDescriptorAndValuePairs readCpuRegisters( const Targets::TargetRegisterDescriptors& descriptors - ) override; - void writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers) override; + ); + void writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers); Targets::TargetMemoryBuffer readMemory( const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, @@ -84,13 +81,13 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec Targets::TargetMemoryAddress startAddress, Targets::TargetMemorySize bytes, const std::set& excludedAddressRanges - ) override; + ); void writeMemory( const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer - ) override; + ); private: static constexpr auto DEBUG_MODULE_RESPONSE_DELAY = std::chrono::microseconds{10}; diff --git a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp index bf5a9ff9..b1ef787e 100644 --- a/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp +++ b/src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp @@ -15,25 +15,6 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec class DebugTransportModuleInterface { public: - /** - * 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. - */ - virtual void activate() = 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 read the value of a debug module register. * diff --git a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp index fdd9f676..899bc7ea 100644 --- a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp +++ b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp @@ -17,10 +17,11 @@ namespace DebugToolDrivers::TargetInterfaces::RiscV class RiscVDebugInterface { public: - virtual void init() = 0; virtual void activate() = 0; virtual void deactivate() = 0; + virtual std::string getDeviceId() = 0; + virtual Targets::TargetExecutionState getExecutionState() = 0; virtual void stop() = 0; @@ -54,5 +55,9 @@ namespace DebugToolDrivers::TargetInterfaces::RiscV Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer ) = 0; + virtual void eraseMemory( + const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor + ) = 0; }; } diff --git a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp deleted file mode 100644 index 7482bf42..00000000 --- a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -namespace DebugToolDrivers::TargetInterfaces::RiscV -{ - class RiscVIdentificationInterface - { - public: - /** - * 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; - }; -} diff --git a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVProgramInterface.hpp b/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVProgramInterface.hpp deleted file mode 100644 index 8e3e202a..00000000 --- a/src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVProgramInterface.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include - -#include "src/Targets/TargetMemory.hpp" -#include "src/Targets/TargetAddressSpaceDescriptor.hpp" -#include "src/Targets/TargetMemorySegmentDescriptor.hpp" - -namespace DebugToolDrivers::TargetInterfaces::RiscV -{ - class RiscVProgramInterface - { - public: - virtual std::optional alignmentSize( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemorySize bufferSize - ) = 0; - - virtual void writeProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemoryBufferSpan buffer - ) = 0; - - virtual void eraseProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor - ) = 0; - }; -} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp index 38d4c798..eec4b1e7 100644 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.cpp @@ -59,61 +59,24 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink }; } - void WchLinkInterface::activate() { - this->setClockSpeed(WchLinkTargetClockSpeed::CLK_6000_KHZ); + void WchLinkInterface::setClockSpeed(WchLinkTargetClockSpeed speed, WchTargetId targetId) { + const auto speedIdsBySpeed = BiMap{ + {WchLinkTargetClockSpeed::CLK_6000_KHZ, 0x01}, + {WchLinkTargetClockSpeed::CLK_4000_KHZ, 0x02}, + {WchLinkTargetClockSpeed::CLK_400_KHZ, 0x03}, + }; - auto response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget{}); - if (response.payload.size() != 5) { - throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for AttachTarget command"}; - } - - this->cachedTargetId = response.payload[0]; - - /* - * For some WCH targets, we must send another command to the debug tool, immediately after attaching. - * - * I don't know what this post-attach command does. But what I *do* know is that the target and/or the debug - * tool will misbehave if we don't send it immediately after the attach. - * - * More specifically, the debug tool will read an invalid target variant ID upon the mutation of the target's - * program buffer. So when we write to progbuf2, progbuf3, progbuf4 or progbuf5, all subsequent reads of the - * target variant ID will yield invalid values, until the target and debug tool have been power cycled. - * Interestingly, when we restore those progbuf registers to their original values, the reading of the target - * variant ID works again. So I suspect the debug tool is using the target's program buffer to read the - * variant ID, but it's assuming the program buffer hasn't changed. Maybe. - * - * So how does this post-attach command fix this issue? I don't know. I just know that it does. - * - * In addition to sending the post-attach command, we have to send another attach command, because the target - * variant ID returned in the response of the first attach command may be invalid. Sending another attach - * command will ensure that we have a valid target variant ID. - */ - if (this->cachedTargetId == 0x09) { - this->sendCommandAndWaitForResponse(Commands::Control::PostAttach{}); - response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget{}); - - if (response.payload.size() != 5) { - throw Exceptions::DeviceCommunicationFailure{ - "Unexpected response payload size for subsequent AttachTarget command" - }; - } - } - - this->cachedVariantId = static_cast( - (response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8) - | (response.payload[4]) + const auto response = this->sendCommandAndWaitForResponse( + Commands::SetClockSpeed{targetId, speedIdsBySpeed.at(speed)} ); - } - void WchLinkInterface::deactivate() { - const auto response = this->sendCommandAndWaitForResponse(Commands::Control::DetachTarget{}); if (response.payload.size() != 1) { - throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for DetachTarget command"}; + throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for SetClockSpeed command"}; } - } - std::string WchLinkInterface::getDeviceId() { - return "0x" + Services::StringService::toHex(this->cachedVariantId.value()); + if (response.payload[0] != 0x01) { + throw Exceptions::DeviceCommunicationFailure{"Unexpected response for SetClockSpeed command"}; + } } DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister(DebugModule::RegisterAddress address) { @@ -275,33 +238,9 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink } this->sendCommandAndWaitForResponse(Commands::EndProgrammingSession{}); - - this->deactivate(); - this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo{}); - this->activate(); } void WchLinkInterface::eraseChip() { this->sendCommandAndWaitForResponse(Commands::EraseChip{}); } - - 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->cachedTargetId.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 e69c9116..2d66c027 100644 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp +++ b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkInterface.hpp @@ -8,8 +8,6 @@ #include #include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp" -#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVProgramInterface.hpp" -#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp" #include "src/DebugToolDrivers/USB/UsbInterface.hpp" #include "src/DebugToolDrivers/USB/UsbDevice.hpp" @@ -21,23 +19,21 @@ #include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" +#include "src/Services/StringService.hpp" + namespace DebugToolDrivers::Wch::Protocols::WchLink { /** - * Implementation of the WCH-Link protocol, which provides an implementation of a RISC-V DTM interface, and a - * target identification interface. + * Implementation of the WCH-Link protocol, which provides an implementation of a RISC-V DTM interface. */ class WchLinkInterface : public ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTransportModuleInterface - , public TargetInterfaces::RiscV::RiscVIdentificationInterface { public: WchLinkInterface(Usb::UsbInterface& usbInterface, Usb::UsbDevice& usbDevice); DeviceInfo getDeviceInfo(); - void activate() override; - void deactivate() override; - std::string getDeviceId() override; + void setClockSpeed(WchLinkTargetClockSpeed speed, WchTargetId targetId); ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterValue readDebugModuleRegister( ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterAddress address @@ -48,42 +44,14 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink ) override; void writePartialPage(Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer); - void writeFullPage( Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer, Targets::TargetMemorySize pageSize, std::span flashProgramOpcodes ); - void eraseChip(); - private: - static constexpr std::uint8_t USB_COMMAND_ENDPOINT_IN = 0x81; - static constexpr std::uint8_t USB_COMMAND_ENDPOINT_OUT = 0x01; - static constexpr std::uint8_t USB_DATA_ENDPOINT_IN = 0x82; - static constexpr std::uint8_t USB_DATA_ENDPOINT_OUT = 0x02; - static constexpr std::uint8_t DMI_OP_MAX_RETRY = 10; - - Usb::UsbInterface& usbInterface; - - std::uint16_t commandEndpointMaxPacketSize = 0; - std::uint16_t dataEndpointMaxPacketSize = 0; - // TODO: Move this into a config param - std::chrono::microseconds dmiOpRetryDelay = std::chrono::microseconds{10}; - - /** - * The 'target activation' command returns a payload of 5 bytes. - * - * The last 4 bytes hold the WCH target variant ID. Given that the 'target activation' command appears to be - * the only way to obtain this ID, we cache it via WchLinkInterface::cachedVariantId and return the cached - * value in WchLinkInterface::getTargetId(). - */ - std::optional cachedVariantId; - std::optional cachedTargetId; - - void setClockSpeed(WchLinkTargetClockSpeed speed); - template auto sendCommandAndWaitForResponse(const CommandType& command) { const auto rawCommand = command.getRawCommand(); @@ -116,12 +84,15 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink } 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"}; + throw Exceptions::DeviceCommunicationFailure{ + "Missing/invalid command ID in response from device 0x" + + Services::StringService::toHex(rawResponse[1]) + " - expected: 0x" + + Services::StringService::toHex(command.commandId) + }; } if ((rawResponse.size() - 3) != rawResponse[2]) { @@ -132,5 +103,19 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink std::vector{rawResponse.begin() + 3, rawResponse.end()} }; } + + private: + static constexpr std::uint8_t USB_COMMAND_ENDPOINT_IN = 0x81; + static constexpr std::uint8_t USB_COMMAND_ENDPOINT_OUT = 0x01; + static constexpr std::uint8_t USB_DATA_ENDPOINT_IN = 0x82; + static constexpr std::uint8_t USB_DATA_ENDPOINT_OUT = 0x02; + static constexpr std::uint8_t DMI_OP_MAX_RETRY = 10; + + Usb::UsbInterface& usbInterface; + + std::uint16_t commandEndpointMaxPacketSize = 0; + std::uint16_t dataEndpointMaxPacketSize = 0; + // TODO: Move this into a config param + std::chrono::microseconds dmiOpRetryDelay = std::chrono::microseconds{10}; }; } diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.cpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.cpp deleted file mode 100644 index f4fe0bbf..00000000 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "WchLinkProgrammingInterface.hpp" - -#include "FlashProgramOpcodes.hpp" - -#include "src/Services/StringService.hpp" -#include "src/Targets/TargetDescription/Exceptions/InvalidTargetDescriptionDataException.hpp" - -namespace DebugToolDrivers::Wch::Protocols::WchLink -{ - using namespace ::DebugToolDrivers::Protocols::RiscVDebugSpec; - using namespace Exceptions; - - using DebugModule::DmiOperation; - - WchLinkProgrammingInterface::WchLinkProgrammingInterface( - WchLinkInterface& wchLinkInterface, - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile - ) - : wchLinkInterface(wchLinkInterface) - , targetDescriptionFile(targetDescriptionFile) - , flashProgramOpcodes( - WchLinkProgrammingInterface::getFlashProgramOpcodes( - this->targetDescriptionFile.getProperty("wch_link_interface", "programming_opcode_key").value - ) - ) - , programmingPacketSize( - Services::StringService::toUint32( - this->targetDescriptionFile.getProperty("wch_link_interface", "programming_packet_size").value - ) - ) - {} - - std::optional WchLinkProgrammingInterface::alignmentSize( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemorySize bufferSize - ) { - return bufferSize > WchLinkProgrammingInterface::MAX_PARTIAL_PAGE_WRITE_SIZE - ? std::optional{this->programmingPacketSize} - : std::nullopt; - } - - void WchLinkProgrammingInterface::writeProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemoryBufferSpan buffer - ) { - if (buffer.size() <= WchLinkProgrammingInterface::MAX_PARTIAL_PAGE_WRITE_SIZE) { - return this->wchLinkInterface.writePartialPage(startAddress, buffer); - } - - this->wchLinkInterface.writeFullPage( - startAddress, - buffer, - this->programmingPacketSize, - this->flashProgramOpcodes - ); - } - - void WchLinkProgrammingInterface::eraseProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor - ) { - this->wchLinkInterface.eraseChip(); - } - - std::span WchLinkProgrammingInterface::getFlashProgramOpcodes(const std::string& key) { - if (key == "op1") { - return FlashProgramOpcodes::FLASH_OP1; - } - - if (key == "op2") { - return FlashProgramOpcodes::FLASH_OP2; - } - - throw Targets::TargetDescription::Exceptions::InvalidTargetDescriptionDataException{ - "Invalid programming_opcode_key value (\"" + key + "\")" - }; - } -} diff --git a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.hpp b/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.hpp deleted file mode 100644 index 360cde77..00000000 --- a/src/DebugToolDrivers/WCH/Protocols/WchLink/WchLinkProgrammingInterface.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "WchLinkInterface.hpp" -#include "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp" - -namespace DebugToolDrivers::Wch::Protocols::WchLink -{ - /** - * WCH debug tools cannot write to program memory via the target's RISC-V debug module, so we cannot program the - * target via the tool's RISC-V DTM interface. Instead, the WCH-Link protocol provides a dedicated command for - * writing to program memory, which is why this class implements the RISC-V programming interface. - * See WchLinkInterface::writeFlashMemory() for more. - */ - class WchLinkProgrammingInterface - : public TargetInterfaces::RiscV::RiscVProgramInterface - { - public: - WchLinkProgrammingInterface( - WchLinkInterface& wchLinkInterface, - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile - ); - - std::optional alignmentSize( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemorySize bufferSize - ) override; - - void writeProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, - Targets::TargetMemoryAddress startAddress, - Targets::TargetMemoryBufferSpan buffer - ) override; - - void eraseProgramMemory( - const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, - const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor - ) override; - - private: - static constexpr Targets::TargetMemorySize MAX_PARTIAL_PAGE_WRITE_SIZE = 64; - - WchLinkInterface& wchLinkInterface; - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile; - - std::span flashProgramOpcodes; - Targets::TargetMemorySize programmingPacketSize; - - static std::span getFlashProgramOpcodes(const std::string& key); - }; -} diff --git a/src/DebugToolDrivers/WCH/WchLinkBase.cpp b/src/DebugToolDrivers/WCH/WchLinkBase.cpp index 0eaec7f9..c3395885 100644 --- a/src/DebugToolDrivers/WCH/WchLinkBase.cpp +++ b/src/DebugToolDrivers/WCH/WchLinkBase.cpp @@ -70,41 +70,20 @@ namespace DebugToolDrivers::Wch return UsbDevice::getSerialNumber(); } - ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator* WchLinkBase::getRiscVDebugInterface( + WchLinkDebugInterface* WchLinkBase::getRiscVDebugInterface( const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, const Targets::RiscV::RiscVTargetConfig& targetConfig ) { - using ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator; - if (!this->wchRiscVTranslator) { - this->wchRiscVTranslator = std::make_unique( - *(this->wchLinkInterface), - this->toolConfig.riscVDebugTranslatorConfig, + if (!this->wchLinkDebugInterface) { + this->wchLinkDebugInterface = std::make_unique( + this->toolConfig, + targetConfig, targetDescriptionFile, - targetConfig + *(this->wchLinkInterface) ); } - return this->wchRiscVTranslator.get(); - } - - Protocols::WchLink::WchLinkProgrammingInterface* WchLinkBase::getRiscVProgramInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) { - if (!this->wchLinkProgrammingInterface) { - this->wchLinkProgrammingInterface = std::make_unique( - *(this->wchLinkInterface), - targetDescriptionFile - ); - } - return this->wchLinkProgrammingInterface.get(); - } - - Protocols::WchLink::WchLinkInterface* WchLinkBase::getRiscVIdentificationInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) { - return this->wchLinkInterface.get(); + return this->wchLinkDebugInterface.get(); } const DeviceInfo& WchLinkBase::getDeviceInfo() const { diff --git a/src/DebugToolDrivers/WCH/WchLinkBase.hpp b/src/DebugToolDrivers/WCH/WchLinkBase.hpp index 31f6db7e..340b7237 100644 --- a/src/DebugToolDrivers/WCH/WchLinkBase.hpp +++ b/src/DebugToolDrivers/WCH/WchLinkBase.hpp @@ -8,13 +8,13 @@ #include "src/DebugToolDrivers/USB/UsbDevice.hpp" #include "src/DebugToolDrivers/USB/UsbInterface.hpp" +#include "Protocols/WchLink/WchLinkInterface.hpp" + +#include "WchLinkDebugInterface.hpp" + #include "WchLinkToolConfig.hpp" #include "src/ProjectConfig.hpp" -#include "Protocols/WchLink/WchLinkInterface.hpp" -#include "Protocols/WchLink/WchLinkProgrammingInterface.hpp" -#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp" - #include "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp" #include "WchGeneric.hpp" @@ -43,29 +43,7 @@ namespace DebugToolDrivers::Wch std::string getSerialNumber() override; - ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator* getRiscVDebugInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) override; - - /** - * WCH-Link debug tools cannot write to flash memory via the RISC-V debug interface (RiscVDebugInterface). - * Flash memory writes via abstract commands fail silently. - * - * We have to send a vendor-specific command to the debug tool, in order to program the target. - * - * For this reason, we have to provide an implementation of the RiscVProgramInterface, so that the RISC-V - * target driver forwards any flash memory writes to this implementation (instead of relying on the debug - * interface). - * - * @return - */ - Protocols::WchLink::WchLinkProgrammingInterface* getRiscVProgramInterface( - const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, - const Targets::RiscV::RiscVTargetConfig& targetConfig - ) override; - - Protocols::WchLink::WchLinkInterface* getRiscVIdentificationInterface( + WchLinkDebugInterface* getRiscVDebugInterface( const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, const Targets::RiscV::RiscVTargetConfig& targetConfig ) override; @@ -79,8 +57,7 @@ namespace DebugToolDrivers::Wch std::uint8_t wchLinkUsbInterfaceNumber; std::unique_ptr wchLinkUsbInterface = nullptr; std::unique_ptr wchLinkInterface = nullptr; - std::unique_ptr wchLinkProgrammingInterface = nullptr; - std::unique_ptr<::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator> wchRiscVTranslator = nullptr; + std::unique_ptr wchLinkDebugInterface = nullptr; mutable std::optional cachedDeviceInfo = std::nullopt; diff --git a/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp new file mode 100644 index 00000000..2b70c0da --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp @@ -0,0 +1,308 @@ +#include "WchLinkDebugInterface.hpp" + +#include "Protocols/WchLink/Commands/Control/AttachTarget.hpp" +#include "Protocols/WchLink/Commands/Control/DetachTarget.hpp" +#include "Protocols/WchLink/Commands/Control/PostAttach.hpp" +#include "Protocols/WchLink/Commands/Control/GetDeviceInfo.hpp" +#include "Protocols/WchLink/Commands/DebugModuleInterfaceOperation.hpp" + +#include "Protocols/WchLink/FlashProgramOpcodes.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Targets/TargetDescription/Exceptions/InvalidTargetDescriptionDataException.hpp" + +#include "src/Logger/Logger.hpp" + +namespace DebugToolDrivers::Wch +{ + using ::Targets::TargetExecutionState; + using ::Targets::TargetMemoryAddress; + using ::Targets::TargetMemoryAddressRange; + using ::Targets::TargetMemorySize; + using ::Targets::TargetMemoryBuffer; + using ::Targets::TargetMemoryBufferSpan; + using ::Targets::TargetStackPointer; + using ::Targets::TargetAddressSpaceDescriptor; + using ::Targets::TargetMemorySegmentDescriptor; + using ::Targets::TargetMemorySegmentType; + using ::Targets::TargetRegisterDescriptors; + using ::Targets::TargetRegisterDescriptorAndValuePairs; + + using namespace Protocols::WchLink; + using namespace ::Exceptions; + + WchLinkDebugInterface::WchLinkDebugInterface( + const WchLinkToolConfig& toolConfig, + const Targets::RiscV::RiscVTargetConfig& targetConfig, + const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, + Protocols::WchLink::WchLinkInterface& wchLinkInterface + ) + : toolConfig(toolConfig) + , targetConfig(targetConfig) + , targetDescriptionFile(targetDescriptionFile) + , wchLinkInterface(wchLinkInterface) + , riscVTranslator( + ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator{ + this->wchLinkInterface, + this->toolConfig.riscVDebugTranslatorConfig, + this->targetDescriptionFile, + this->targetConfig + } + ) + , flashProgramOpcodes( + WchLinkDebugInterface::getFlashProgramOpcodes( + this->targetDescriptionFile.getProperty("wch_link_interface", "programming_opcode_key").value + ) + ) + , programmingPacketSize( + Services::StringService::toUint32( + this->targetDescriptionFile.getProperty("wch_link_interface", "programming_packet_size").value + ) + ) + {} + + void WchLinkDebugInterface::activate() { + this->wchLinkInterface.setClockSpeed( + WchLinkTargetClockSpeed::CLK_6000_KHZ, + this->cachedTargetId.value_or(0x01) + ); + + auto response = this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::AttachTarget{}); + if (response.payload.size() != 5) { + throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for AttachTarget command"}; + } + + this->cachedTargetId = response.payload[0]; + + /* + * For some WCH targets, we must send another command to the debug tool, immediately after attaching. + * + * I don't know what this post-attach command does. But what I *do* know is that the target and/or the debug + * tool will misbehave if we don't send it immediately after the attach. + * + * More specifically, the debug tool will read an invalid target variant ID upon the mutation of the target's + * program buffer. So when we write to progbuf2, progbuf3, progbuf4 or progbuf5, all subsequent reads of the + * target variant ID will yield invalid values, until the target and debug tool have been power cycled. + * Interestingly, when we restore those progbuf registers to their original values, the reading of the target + * variant ID works again. So I suspect the debug tool is using the target's program buffer to read the + * variant ID, but it's assuming the program buffer hasn't changed. Maybe. + * + * So how does this post-attach command fix this issue? I don't know. I just know that it does. + * + * In addition to sending the post-attach command, we have to send another attach command, because the target + * variant ID returned in the response of the first attach command may be invalid. Sending another attach + * command will ensure that we have a valid target variant ID. + */ + if (this->cachedTargetId == 0x09) { + this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::PostAttach{}); + response = this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::AttachTarget{}); + + if (response.payload.size() != 5) { + throw Exceptions::DeviceCommunicationFailure{ + "Unexpected response payload size for subsequent AttachTarget command" + }; + } + } + + this->cachedVariantId = static_cast( + (response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8) + | (response.payload[4]) + ); + + this->riscVTranslator.activate(); + } + + void WchLinkDebugInterface::deactivate() { + this->riscVTranslator.deactivate(); + + const auto response = this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::DetachTarget{}); + if (response.payload.size() != 1) { + throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for DetachTarget command"}; + } + } + + std::string WchLinkDebugInterface::getDeviceId() { + return "0x" + Services::StringService::toHex(this->cachedVariantId.value()); + } + + Targets::TargetExecutionState WchLinkDebugInterface::getExecutionState() { + return this->riscVTranslator.getExecutionState(); + } + + void WchLinkDebugInterface::stop() { + this->riscVTranslator.stop(); + } + + void WchLinkDebugInterface::run() { + this->riscVTranslator.run(); + } + + void WchLinkDebugInterface::step() { + this->riscVTranslator.step(); + } + + void WchLinkDebugInterface::reset() { + this->riscVTranslator.reset(); + } + + void WchLinkDebugInterface::setSoftwareBreakpoint(Targets::TargetMemoryAddress address) { + throw Exceptions::Exception{"SW breakpoints not supported"}; + } + + void WchLinkDebugInterface::clearSoftwareBreakpoint(Targets::TargetMemoryAddress address) { + throw Exceptions::Exception{"SW breakpoints not supported"}; + } + + std::uint16_t WchLinkDebugInterface::getHardwareBreakpointCount() { + return this->riscVTranslator.getTriggerCount(); + } + + void WchLinkDebugInterface::setHardwareBreakpoint(Targets::TargetMemoryAddress address) { + this->riscVTranslator.insertTriggerBreakpoint(address); + } + + void WchLinkDebugInterface::clearHardwareBreakpoint(Targets::TargetMemoryAddress address) { + this->riscVTranslator.clearTriggerBreakpoint(address); + } + + void WchLinkDebugInterface::clearAllHardwareBreakpoints() { + this->riscVTranslator.clearAllTriggerBreakpoints(); + } + + Targets::TargetRegisterDescriptorAndValuePairs WchLinkDebugInterface::readCpuRegisters( + const Targets::TargetRegisterDescriptors& descriptors + ) { + return this->riscVTranslator.readCpuRegisters(descriptors); + } + + void WchLinkDebugInterface::writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers) { + return this->riscVTranslator.writeCpuRegisters(registers); + } + + Targets::TargetMemoryBuffer WchLinkDebugInterface::readMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + Targets::TargetMemoryAddress startAddress, + Targets::TargetMemorySize bytes, + const std::set& excludedAddressRanges + ) { + return this->riscVTranslator.readMemory( + addressSpaceDescriptor, + memorySegmentDescriptor, + startAddress, + bytes, + excludedAddressRanges + ); + } + + void WchLinkDebugInterface::writeMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor, + Targets::TargetMemoryAddress startAddress, + Targets::TargetMemoryBufferSpan buffer + ) { + if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) { + const auto bufferSize = static_cast(buffer.size()); + const auto alignmentSize = bufferSize > WchLinkDebugInterface::MAX_PARTIAL_PAGE_WRITE_SIZE + ? this->programmingPacketSize + : 1; + + if (alignmentSize > 1) { + const auto alignedStartAddress = (startAddress / alignmentSize) * alignmentSize; + const auto alignedBufferSize = static_cast(std::ceil( + static_cast(bufferSize) / static_cast(alignmentSize) + ) * alignmentSize); + + if (alignedStartAddress != startAddress || alignedBufferSize != bufferSize) { + auto alignedBuffer = (alignedStartAddress < startAddress) + ? this->readMemory( + addressSpaceDescriptor, + memorySegmentDescriptor, + alignedStartAddress, + (startAddress - alignedStartAddress), + {} + ) + : TargetMemoryBuffer{}; + + alignedBuffer.resize(alignedBufferSize); + + std::copy( + buffer.begin(), + buffer.end(), + alignedBuffer.begin() + (startAddress - alignedStartAddress) + ); + + const auto dataBack = this->readMemory( + addressSpaceDescriptor, + memorySegmentDescriptor, + startAddress + bufferSize, + alignedBufferSize - bufferSize - (startAddress - alignedStartAddress), + {} + ); + std::copy( + dataBack.begin(), + dataBack.end(), + alignedBuffer.begin() + (startAddress - alignedStartAddress) + bufferSize + ); + + return this->writeFlashMemory(alignedStartAddress, alignedBuffer); + } + } + + return this->writeFlashMemory(startAddress, buffer); + } + + this->riscVTranslator.writeMemory( + addressSpaceDescriptor, + memorySegmentDescriptor, + startAddress, + buffer + ); + } + + void WchLinkDebugInterface::eraseMemory( + const TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const TargetMemorySegmentDescriptor& memorySegmentDescriptor + ) { + if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) { + return this->eraseFlashMemory(); + } + + throw Exception{"Erasing non-flash memory not supported in WchLinkDebugInterface"}; + } + + void WchLinkDebugInterface::writeFlashMemory(TargetMemoryAddress startAddress, TargetMemoryBufferSpan buffer) { + if (buffer.size() <= WchLinkDebugInterface::MAX_PARTIAL_PAGE_WRITE_SIZE) { + return this->wchLinkInterface.writePartialPage(startAddress, buffer); + } + + this->wchLinkInterface.writeFullPage( + startAddress, + buffer, + this->programmingPacketSize, + this->flashProgramOpcodes + ); + + this->deactivate(); + this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo{}); + this->activate(); + } + + void WchLinkDebugInterface::eraseFlashMemory() { + this->wchLinkInterface.eraseChip(); + } + + std::span WchLinkDebugInterface::getFlashProgramOpcodes(const std::string& key) { + if (key == "op1") { + return FlashProgramOpcodes::FLASH_OP1; + } + + if (key == "op2") { + return FlashProgramOpcodes::FLASH_OP2; + } + + throw Targets::TargetDescription::Exceptions::InvalidTargetDescriptionDataException{ + "Invalid programming_opcode_key value (\"" + key + "\")" + }; + } +} diff --git a/src/DebugToolDrivers/WCH/WchLinkDebugInterface.hpp b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.hpp new file mode 100644 index 00000000..ca625963 --- /dev/null +++ b/src/DebugToolDrivers/WCH/WchLinkDebugInterface.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include + +#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp" + +#include "Protocols/WchLink/WchLinkInterface.hpp" + +#include "WchLinkToolConfig.hpp" +#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTranslator.hpp" + +#include "src/Targets/TargetMemory.hpp" +#include "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp" + +namespace DebugToolDrivers::Wch +{ + /** + * The RISC-V debug module cannot provide a complete implementation of the RiscVDebugInterface. + * + * This class combines the functionality of the RISC-V debug module (via the RiscVDebugTranslator), with the + * WCH-Link-specific functionality, to provide a complete implementation. + */ + class WchLinkDebugInterface + : public TargetInterfaces::RiscV::RiscVDebugInterface + { + public: + WchLinkDebugInterface( + const WchLinkToolConfig& toolConfig, + const Targets::RiscV::RiscVTargetConfig& targetConfig, + const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile, + Protocols::WchLink::WchLinkInterface& wchLinkInterface + ); + + void activate() override; + void deactivate() override; + + std::string getDeviceId() override; + + Targets::TargetExecutionState getExecutionState() override; + + void stop() override; + void run() override; + void step() override; + void reset() override; + + void setSoftwareBreakpoint(Targets::TargetMemoryAddress address) override; + void clearSoftwareBreakpoint(Targets::TargetMemoryAddress address) override; + + std::uint16_t getHardwareBreakpointCount() override; + void setHardwareBreakpoint(Targets::TargetMemoryAddress address) override; + void clearHardwareBreakpoint(Targets::TargetMemoryAddress address) override; + void clearAllHardwareBreakpoints() override; + + Targets::TargetRegisterDescriptorAndValuePairs readCpuRegisters( + const Targets::TargetRegisterDescriptors& descriptors + ) override; + void writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers) override; + + Targets::TargetMemoryBuffer readMemory( + const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, + Targets::TargetMemoryAddress startAddress, + Targets::TargetMemorySize bytes, + const std::set& excludedAddressRanges + ) override; + void writeMemory( + const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, + Targets::TargetMemoryAddress startAddress, + Targets::TargetMemoryBufferSpan buffer + ) override; + void eraseMemory( + const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, + const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor + ) override; + + private: + static constexpr Targets::TargetMemorySize MAX_PARTIAL_PAGE_WRITE_SIZE = 64; + + const WchLinkToolConfig& toolConfig; + const Targets::RiscV::RiscVTargetConfig& targetConfig; + const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile; + + Protocols::WchLink::WchLinkInterface& wchLinkInterface; + DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator riscVTranslator; + + /** + * The 'target activation' command returns a payload of 5 bytes. + * + * The last 4 bytes hold the WCH target variant ID. Given that the 'target activation' command appears to be + * the only way to obtain this ID, we cache it via WchLinkInterface::cachedVariantId and return the cached + * value in WchLinkInterface::getTargetId(). + */ + std::optional cachedVariantId; + std::optional cachedTargetId; + + std::span flashProgramOpcodes; + Targets::TargetMemorySize programmingPacketSize; + + void writeFlashMemory(Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer); + void eraseFlashMemory(); + + static std::span getFlashProgramOpcodes(const std::string& key); + }; +} diff --git a/src/DebugToolDrivers/WCH/WchLinkToolConfig.cpp b/src/DebugToolDrivers/WCH/WchLinkToolConfig.cpp index 050562e9..810baf90 100644 --- a/src/DebugToolDrivers/WCH/WchLinkToolConfig.cpp +++ b/src/DebugToolDrivers/WCH/WchLinkToolConfig.cpp @@ -8,7 +8,7 @@ namespace DebugToolDrivers::Wch const auto& toolNode = toolConfig.toolNode; if (toolNode["riscVDebugTranslator"]) { - this->riscVDebugTranslatorConfig = Protocols::RiscVDebugSpec::DebugTranslatorConfig{ + this->riscVDebugTranslatorConfig = ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslatorConfig{ toolNode["riscVDebugTranslator"] }; } diff --git a/src/DebugToolDrivers/WCH/WchLinkToolConfig.hpp b/src/DebugToolDrivers/WCH/WchLinkToolConfig.hpp index 08176e7f..2ce70a99 100644 --- a/src/DebugToolDrivers/WCH/WchLinkToolConfig.hpp +++ b/src/DebugToolDrivers/WCH/WchLinkToolConfig.hpp @@ -13,7 +13,7 @@ namespace DebugToolDrivers::Wch struct WchLinkToolConfig: public DebugToolConfig { public: - Protocols::RiscVDebugSpec::DebugTranslatorConfig riscVDebugTranslatorConfig = {}; + ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslatorConfig riscVDebugTranslatorConfig = {}; explicit WchLinkToolConfig(const DebugToolConfig& toolConfig); }; diff --git a/src/Targets/RiscV/RiscV.cpp b/src/Targets/RiscV/RiscV.cpp index 73639e52..acf9c1e6 100644 --- a/src/Targets/RiscV/RiscV.cpp +++ b/src/Targets/RiscV/RiscV.cpp @@ -39,23 +39,11 @@ namespace Targets::RiscV {} bool RiscV::supportsDebugTool(DebugTool* debugTool) { - return - debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig) != nullptr - && debugTool->getRiscVProgramInterface(this->targetDescriptionFile, this->targetConfig) != nullptr - && debugTool->getRiscVIdentificationInterface(this->targetDescriptionFile, this->targetConfig) != nullptr - ; + return debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig) != nullptr; } void RiscV::setDebugTool(DebugTool* debugTool) { this->riscVDebugInterface = debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig); - this->riscVProgramInterface = debugTool->getRiscVProgramInterface( - this->targetDescriptionFile, - this->targetConfig - ); - this->riscVIdInterface = debugTool->getRiscVIdentificationInterface( - this->targetDescriptionFile, - this->targetConfig - ); } void RiscV::activate() { @@ -249,78 +237,6 @@ namespace Targets::RiscV static_cast(startAddress + buffer.size()) - 1) ); - if ( - memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH - && this->isProgramMemory( - addressSpaceDescriptor, - memorySegmentDescriptor, - startAddress, - static_cast(buffer.size()) - ) - ) { - const auto alignmentSize = this->riscVProgramInterface->alignmentSize( - addressSpaceDescriptor, - memorySegmentDescriptor, - startAddress, - static_cast(buffer.size()) - ); - - if (alignmentSize.has_value()) { - const auto bufferSize = static_cast(buffer.size()); - const auto alignedStartAddress = (startAddress / *alignmentSize) * *alignmentSize; - const auto alignedBufferSize = static_cast(std::ceil( - static_cast(bufferSize) / static_cast(*alignmentSize) - ) * *alignmentSize); - - if (alignedStartAddress != startAddress || alignedBufferSize != bufferSize) { - auto alignedBuffer = (alignedStartAddress < startAddress) - ? this->readMemory( - addressSpaceDescriptor, - memorySegmentDescriptor, - alignedStartAddress, - (startAddress - alignedStartAddress), - {} - ) - : TargetMemoryBuffer{}; - - alignedBuffer.resize(alignedBufferSize); - - std::copy( - buffer.begin(), - buffer.end(), - alignedBuffer.begin() + (startAddress - alignedStartAddress) - ); - - const auto dataBack = this->readMemory( - addressSpaceDescriptor, - memorySegmentDescriptor, - startAddress + bufferSize, - alignedBufferSize - bufferSize - (startAddress - alignedStartAddress), - {} - ); - std::copy( - dataBack.begin(), - dataBack.end(), - alignedBuffer.begin() + (startAddress - alignedStartAddress) + bufferSize - ); - - return this->riscVProgramInterface->writeProgramMemory( - addressSpaceDescriptor, - memorySegmentDescriptor, - alignedStartAddress, - alignedBuffer - ); - } - } - - return this->riscVProgramInterface->writeProgramMemory( - addressSpaceDescriptor, - memorySegmentDescriptor, - startAddress, - buffer - ); - } - return this->riscVDebugInterface->writeMemory( addressSpaceDescriptor, memorySegmentDescriptor, @@ -342,7 +258,7 @@ namespace Targets::RiscV const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor ) { - this->riscVProgramInterface->eraseProgramMemory(addressSpaceDescriptor, memorySegmentDescriptor); + this->riscVDebugInterface->eraseMemory(addressSpaceDescriptor, memorySegmentDescriptor); } TargetExecutionState RiscV::getExecutionState() { @@ -358,7 +274,6 @@ namespace Targets::RiscV } void RiscV::setProgramCounter(TargetMemoryAddress programCounter) { - // TODO: test this this->riscVDebugInterface->writeCpuRegisters({ { this->pcRegisterDescriptor, diff --git a/src/Targets/RiscV/RiscV.hpp b/src/Targets/RiscV/RiscV.hpp index 895c21be..67a3e2ea 100644 --- a/src/Targets/RiscV/RiscV.hpp +++ b/src/Targets/RiscV/RiscV.hpp @@ -11,8 +11,6 @@ #include "TargetDescriptionFile.hpp" #include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp" -#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVProgramInterface.hpp" -#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVIdentificationInterface.hpp" namespace Targets::RiscV { @@ -97,8 +95,6 @@ namespace Targets::RiscV TargetDescriptionFile targetDescriptionFile; DebugToolDrivers::TargetInterfaces::RiscV::RiscVDebugInterface* riscVDebugInterface = nullptr; - DebugToolDrivers::TargetInterfaces::RiscV::RiscVProgramInterface* riscVProgramInterface = nullptr; - DebugToolDrivers::TargetInterfaces::RiscV::RiscVIdentificationInterface* riscVIdInterface = nullptr; /* * On RISC-V targets, CPU registers are typically only accessible via the debug module (we can't access them diff --git a/src/Targets/RiscV/Wch/WchRiscV.cpp b/src/Targets/RiscV/Wch/WchRiscV.cpp index e06fff24..ff9dfdb9 100644 --- a/src/Targets/RiscV/Wch/WchRiscV.cpp +++ b/src/Targets/RiscV/Wch/WchRiscV.cpp @@ -26,7 +26,7 @@ namespace Targets::RiscV::Wch * the variant ID. */ const auto variantsById = this->targetDescriptionFile.getVariantsByWchVariantId(); - const auto deviceId = this->riscVIdInterface->getDeviceId(); + const auto deviceId = this->riscVDebugInterface->getDeviceId(); const auto variantIt = variantsById.find(deviceId); if (variantIt == variantsById.end()) {