#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 + "\")" }; } }