Refactored WCH-Link/RISC-V implementation to accommodate SW breakpoints and reduce complexity
This commit is contained in:
@@ -45,7 +45,7 @@ target_sources(
|
|||||||
|
|
||||||
# WCH debug tools and interface implementations
|
# WCH debug tools and interface implementations
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WCH/Protocols/WchLink/WchLinkInterface.cpp
|
${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/WchLinkToolConfig.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkBase.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkBase.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkE/WchLinkE.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/WCH/WchLinkE/WchLinkE.cpp
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
#include "src/Targets/Microchip/AVR8/Avr8TargetConfig.hpp"
|
#include "src/Targets/Microchip/AVR8/Avr8TargetConfig.hpp"
|
||||||
|
|
||||||
#include "TargetInterfaces/RiscV/RiscVDebugInterface.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/TargetDescriptionFile.hpp"
|
||||||
#include "src/Targets/RiscV/RiscVTargetConfig.hpp"
|
#include "src/Targets/RiscV/RiscVTargetConfig.hpp"
|
||||||
|
|
||||||
@@ -122,45 +120,4 @@ public:
|
|||||||
) {
|
) {
|
||||||
return nullptr;
|
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,13 +77,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
, targetConfig(targetConfig)
|
, targetConfig(targetConfig)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void DebugTranslator::init() {
|
|
||||||
// No pre-activation initialisation required.
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugTranslator::activate() {
|
void DebugTranslator::activate() {
|
||||||
this->dtmInterface.activate();
|
|
||||||
|
|
||||||
this->debugModuleDescriptor.hartIndices = this->discoverHartIndices();
|
this->debugModuleDescriptor.hartIndices = this->discoverHartIndices();
|
||||||
if (this->debugModuleDescriptor.hartIndices.empty()) {
|
if (this->debugModuleDescriptor.hartIndices.empty()) {
|
||||||
throw Exceptions::TargetOperationFailure{"Failed to discover any RISC-V harts"};
|
throw Exceptions::TargetOperationFailure{"Failed to discover any RISC-V harts"};
|
||||||
@@ -122,7 +116,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
|
|
||||||
if (!this->debugModuleDescriptor.triggerDescriptorsByIndex.empty()) {
|
if (!this->debugModuleDescriptor.triggerDescriptorsByIndex.empty()) {
|
||||||
// Clear any left-over triggers from the previous debug session
|
// Clear any left-over triggers from the previous debug session
|
||||||
this->clearAllHardwareBreakpoints();
|
this->clearAllTriggerBreakpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->initDebugControlStatusRegister();
|
this->initDebugControlStatusRegister();
|
||||||
@@ -177,7 +171,6 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
|
|
||||||
void DebugTranslator::deactivate() {
|
void DebugTranslator::deactivate() {
|
||||||
this->disableDebugModule();
|
this->disableDebugModule();
|
||||||
this->dtmInterface.deactivate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetExecutionState DebugTranslator::getExecutionState() {
|
TargetExecutionState DebugTranslator::getExecutionState() {
|
||||||
@@ -305,19 +298,11 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
this->initDebugControlStatusRegister();
|
this->initDebugControlStatusRegister();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugTranslator::setSoftwareBreakpoint(TargetMemoryAddress address) {
|
std::uint16_t DebugTranslator::getTriggerCount() const {
|
||||||
throw Exceptions::Exception{"SW breakpoints not supported"};
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugTranslator::clearSoftwareBreakpoint(TargetMemoryAddress address) {
|
|
||||||
throw Exceptions::Exception{"SW breakpoints not supported"};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint16_t DebugTranslator::getHardwareBreakpointCount() {
|
|
||||||
return static_cast<std::uint16_t>(this->debugModuleDescriptor.triggerDescriptorsByIndex.size());
|
return static_cast<std::uint16_t>(this->debugModuleDescriptor.triggerDescriptorsByIndex.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugTranslator::setHardwareBreakpoint(TargetMemoryAddress address) {
|
void DebugTranslator::insertTriggerBreakpoint(TargetMemoryAddress address) {
|
||||||
using TriggerModule::TriggerType;
|
using TriggerModule::TriggerType;
|
||||||
|
|
||||||
const auto triggerDescriptorOpt = this->getAvailableTrigger();
|
const auto triggerDescriptorOpt = this->getAvailableTrigger();
|
||||||
@@ -361,7 +346,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
throw Exceptions::Exception{"Unsupported trigger"};
|
throw Exceptions::Exception{"Unsupported trigger"};
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugTranslator::clearHardwareBreakpoint(TargetMemoryAddress address) {
|
void DebugTranslator::clearTriggerBreakpoint(TargetMemoryAddress address) {
|
||||||
const auto triggerIndexIt = this->triggerIndicesByBreakpointAddress.find(address);
|
const auto triggerIndexIt = this->triggerIndicesByBreakpointAddress.find(address);
|
||||||
if (triggerIndexIt == this->triggerIndicesByBreakpointAddress.end()) {
|
if (triggerIndexIt == this->triggerIndicesByBreakpointAddress.end()) {
|
||||||
throw Exceptions::Exception{"Unknown hardware breakpoint"};
|
throw Exceptions::Exception{"Unknown hardware breakpoint"};
|
||||||
@@ -374,7 +359,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
this->allocatedTriggerIndices.erase(triggerDescriptor.index);
|
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.
|
// To ensure that any untracked breakpoints are cleared, we clear all triggers on the target.
|
||||||
for (const auto& [triggerIndex, triggerDescriptor] : this->debugModuleDescriptor.triggerDescriptorsByIndex) {
|
for (const auto& [triggerIndex, triggerDescriptor] : this->debugModuleDescriptor.triggerDescriptorsByIndex) {
|
||||||
this->clearTrigger(triggerDescriptor);
|
this->clearTrigger(triggerDescriptor);
|
||||||
|
|||||||
@@ -10,16 +10,17 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp"
|
|
||||||
|
|
||||||
#include "DebugTransportModuleInterface.hpp"
|
#include "DebugTransportModuleInterface.hpp"
|
||||||
#include "DebugTranslatorConfig.hpp"
|
#include "DebugTranslatorConfig.hpp"
|
||||||
#include "DebugModuleDescriptor.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/TargetDescriptionFile.hpp"
|
||||||
#include "src/Targets/RiscV/RiscVTargetConfig.hpp"
|
#include "src/Targets/RiscV/RiscVTargetConfig.hpp"
|
||||||
#include "src/Targets/RiscV/Opcodes/Opcode.hpp"
|
#include "src/Targets/RiscV/Opcodes/Opcode.hpp"
|
||||||
#include "src/Targets/TargetMemory.hpp"
|
|
||||||
|
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
#include "Registers/CpuRegisterNumbers.hpp"
|
#include "Registers/CpuRegisterNumbers.hpp"
|
||||||
@@ -42,7 +43,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
/**
|
/**
|
||||||
* Implementation of a RISC-V debug translator
|
* Implementation of a RISC-V debug translator
|
||||||
*/
|
*/
|
||||||
class DebugTranslator: public ::DebugToolDrivers::TargetInterfaces::RiscV::RiscVDebugInterface
|
class DebugTranslator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DebugTranslator(
|
DebugTranslator(
|
||||||
@@ -54,29 +55,25 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
|
|
||||||
virtual ~DebugTranslator() = default;
|
virtual ~DebugTranslator() = default;
|
||||||
|
|
||||||
void init() override;
|
void activate();
|
||||||
void activate() override;
|
void deactivate();
|
||||||
void deactivate() override;
|
|
||||||
|
|
||||||
Targets::TargetExecutionState getExecutionState() override;
|
Targets::TargetExecutionState getExecutionState();
|
||||||
|
|
||||||
void stop() override;
|
void stop();
|
||||||
void run() override;
|
void run();
|
||||||
void step() override;
|
void step();
|
||||||
void reset() override;
|
void reset();
|
||||||
|
|
||||||
void setSoftwareBreakpoint(Targets::TargetMemoryAddress address) override;
|
std::uint16_t getTriggerCount() const;
|
||||||
void clearSoftwareBreakpoint(Targets::TargetMemoryAddress address) override;
|
void insertTriggerBreakpoint(Targets::TargetMemoryAddress address);
|
||||||
|
void clearTriggerBreakpoint(Targets::TargetMemoryAddress address);
|
||||||
std::uint16_t getHardwareBreakpointCount() override;
|
void clearAllTriggerBreakpoints();
|
||||||
void setHardwareBreakpoint(Targets::TargetMemoryAddress address) override;
|
|
||||||
void clearHardwareBreakpoint(Targets::TargetMemoryAddress address) override;
|
|
||||||
void clearAllHardwareBreakpoints() override;
|
|
||||||
|
|
||||||
Targets::TargetRegisterDescriptorAndValuePairs readCpuRegisters(
|
Targets::TargetRegisterDescriptorAndValuePairs readCpuRegisters(
|
||||||
const Targets::TargetRegisterDescriptors& descriptors
|
const Targets::TargetRegisterDescriptors& descriptors
|
||||||
) override;
|
);
|
||||||
void writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers) override;
|
void writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers);
|
||||||
|
|
||||||
Targets::TargetMemoryBuffer readMemory(
|
Targets::TargetMemoryBuffer readMemory(
|
||||||
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
@@ -84,13 +81,13 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
Targets::TargetMemoryAddress startAddress,
|
Targets::TargetMemoryAddress startAddress,
|
||||||
Targets::TargetMemorySize bytes,
|
Targets::TargetMemorySize bytes,
|
||||||
const std::set<Targets::TargetMemoryAddressRange>& excludedAddressRanges
|
const std::set<Targets::TargetMemoryAddressRange>& excludedAddressRanges
|
||||||
) override;
|
);
|
||||||
void writeMemory(
|
void writeMemory(
|
||||||
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
Targets::TargetMemoryAddress startAddress,
|
Targets::TargetMemoryAddress startAddress,
|
||||||
Targets::TargetMemoryBufferSpan buffer
|
Targets::TargetMemoryBufferSpan buffer
|
||||||
) override;
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr auto DEBUG_MODULE_RESPONSE_DELAY = std::chrono::microseconds{10};
|
static constexpr auto DEBUG_MODULE_RESPONSE_DELAY = std::chrono::microseconds{10};
|
||||||
|
|||||||
@@ -15,25 +15,6 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
class DebugTransportModuleInterface
|
class DebugTransportModuleInterface
|
||||||
{
|
{
|
||||||
public:
|
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.
|
* Should read the value of a debug module register.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ namespace DebugToolDrivers::TargetInterfaces::RiscV
|
|||||||
class RiscVDebugInterface
|
class RiscVDebugInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void init() = 0;
|
|
||||||
virtual void activate() = 0;
|
virtual void activate() = 0;
|
||||||
virtual void deactivate() = 0;
|
virtual void deactivate() = 0;
|
||||||
|
|
||||||
|
virtual std::string getDeviceId() = 0;
|
||||||
|
|
||||||
virtual Targets::TargetExecutionState getExecutionState() = 0;
|
virtual Targets::TargetExecutionState getExecutionState() = 0;
|
||||||
|
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
@@ -54,5 +55,9 @@ namespace DebugToolDrivers::TargetInterfaces::RiscV
|
|||||||
Targets::TargetMemoryAddress startAddress,
|
Targets::TargetMemoryAddress startAddress,
|
||||||
Targets::TargetMemoryBufferSpan buffer
|
Targets::TargetMemoryBufferSpan buffer
|
||||||
) = 0;
|
) = 0;
|
||||||
|
virtual void eraseMemory(
|
||||||
|
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor
|
||||||
|
) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#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<Targets::TargetMemorySize> 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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -59,61 +59,24 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void WchLinkInterface::activate() {
|
void WchLinkInterface::setClockSpeed(WchLinkTargetClockSpeed speed, WchTargetId targetId) {
|
||||||
this->setClockSpeed(WchLinkTargetClockSpeed::CLK_6000_KHZ);
|
const auto speedIdsBySpeed = BiMap<WchLinkTargetClockSpeed, std::uint8_t>{
|
||||||
|
{WchLinkTargetClockSpeed::CLK_6000_KHZ, 0x01},
|
||||||
|
{WchLinkTargetClockSpeed::CLK_4000_KHZ, 0x02},
|
||||||
|
{WchLinkTargetClockSpeed::CLK_400_KHZ, 0x03},
|
||||||
|
};
|
||||||
|
|
||||||
auto response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget{});
|
const auto response = this->sendCommandAndWaitForResponse(
|
||||||
if (response.payload.size() != 5) {
|
Commands::SetClockSpeed{targetId, speedIdsBySpeed.at(speed)}
|
||||||
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<WchTargetVariantId>(
|
|
||||||
(response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8)
|
|
||||||
| (response.payload[4])
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
void WchLinkInterface::deactivate() {
|
|
||||||
const auto response = this->sendCommandAndWaitForResponse(Commands::Control::DetachTarget{});
|
|
||||||
if (response.payload.size() != 1) {
|
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() {
|
if (response.payload[0] != 0x01) {
|
||||||
return "0x" + Services::StringService::toHex(this->cachedVariantId.value());
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response for SetClockSpeed command"};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister(DebugModule::RegisterAddress address) {
|
DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister(DebugModule::RegisterAddress address) {
|
||||||
@@ -275,33 +238,9 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->sendCommandAndWaitForResponse(Commands::EndProgrammingSession{});
|
this->sendCommandAndWaitForResponse(Commands::EndProgrammingSession{});
|
||||||
|
|
||||||
this->deactivate();
|
|
||||||
this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo{});
|
|
||||||
this->activate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WchLinkInterface::eraseChip() {
|
void WchLinkInterface::eraseChip() {
|
||||||
this->sendCommandAndWaitForResponse(Commands::EraseChip{});
|
this->sendCommandAndWaitForResponse(Commands::EraseChip{});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WchLinkInterface::setClockSpeed(WchLinkTargetClockSpeed speed) {
|
|
||||||
const auto speedIdsBySpeed = BiMap<WchLinkTargetClockSpeed, std::uint8_t>{
|
|
||||||
{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"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/DebugTransportModuleInterface.hpp"
|
#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/UsbInterface.hpp"
|
||||||
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
||||||
@@ -21,23 +19,21 @@
|
|||||||
|
|
||||||
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
|
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
|
||||||
|
|
||||||
|
#include "src/Services/StringService.hpp"
|
||||||
|
|
||||||
namespace DebugToolDrivers::Wch::Protocols::WchLink
|
namespace DebugToolDrivers::Wch::Protocols::WchLink
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Implementation of the WCH-Link protocol, which provides an implementation of a RISC-V DTM interface, and a
|
* Implementation of the WCH-Link protocol, which provides an implementation of a RISC-V DTM interface.
|
||||||
* target identification interface.
|
|
||||||
*/
|
*/
|
||||||
class WchLinkInterface
|
class WchLinkInterface
|
||||||
: public ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTransportModuleInterface
|
: public ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTransportModuleInterface
|
||||||
, public TargetInterfaces::RiscV::RiscVIdentificationInterface
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WchLinkInterface(Usb::UsbInterface& usbInterface, Usb::UsbDevice& usbDevice);
|
WchLinkInterface(Usb::UsbInterface& usbInterface, Usb::UsbDevice& usbDevice);
|
||||||
|
|
||||||
DeviceInfo getDeviceInfo();
|
DeviceInfo getDeviceInfo();
|
||||||
void activate() override;
|
void setClockSpeed(WchLinkTargetClockSpeed speed, WchTargetId targetId);
|
||||||
void deactivate() override;
|
|
||||||
std::string getDeviceId() override;
|
|
||||||
|
|
||||||
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterValue readDebugModuleRegister(
|
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterValue readDebugModuleRegister(
|
||||||
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterAddress address
|
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugModule::RegisterAddress address
|
||||||
@@ -48,42 +44,14 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|||||||
) override;
|
) override;
|
||||||
|
|
||||||
void writePartialPage(Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer);
|
void writePartialPage(Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer);
|
||||||
|
|
||||||
void writeFullPage(
|
void writeFullPage(
|
||||||
Targets::TargetMemoryAddress startAddress,
|
Targets::TargetMemoryAddress startAddress,
|
||||||
Targets::TargetMemoryBufferSpan buffer,
|
Targets::TargetMemoryBufferSpan buffer,
|
||||||
Targets::TargetMemorySize pageSize,
|
Targets::TargetMemorySize pageSize,
|
||||||
std::span<const unsigned char> flashProgramOpcodes
|
std::span<const unsigned char> flashProgramOpcodes
|
||||||
);
|
);
|
||||||
|
|
||||||
void eraseChip();
|
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<WchTargetVariantId> cachedVariantId;
|
|
||||||
std::optional<WchTargetId> cachedTargetId;
|
|
||||||
|
|
||||||
void setClockSpeed(WchLinkTargetClockSpeed speed);
|
|
||||||
|
|
||||||
template <class CommandType>
|
template <class CommandType>
|
||||||
auto sendCommandAndWaitForResponse(const CommandType& command) {
|
auto sendCommandAndWaitForResponse(const CommandType& command) {
|
||||||
const auto rawCommand = command.getRawCommand();
|
const auto rawCommand = command.getRawCommand();
|
||||||
@@ -116,12 +84,15 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rawResponse[0] == 0x81) {
|
if (rawResponse[0] == 0x81) {
|
||||||
// TODO: Create ErrorResponse exception class and throw an instance of it here.
|
|
||||||
throw Exceptions::DeviceCommunicationFailure{"Error response"};
|
throw Exceptions::DeviceCommunicationFailure{"Error response"};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawResponse[1] != command.commandId) {
|
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]) {
|
if ((rawResponse.size() - 3) != rawResponse[2]) {
|
||||||
@@ -132,5 +103,19 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|||||||
std::vector<unsigned char>{rawResponse.begin() + 3, rawResponse.end()}
|
std::vector<unsigned char>{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};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<Targets::TargetMemorySize> 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<const unsigned char> 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 + "\")"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <span>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#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<Targets::TargetMemorySize> 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<const unsigned char> flashProgramOpcodes;
|
|
||||||
Targets::TargetMemorySize programmingPacketSize;
|
|
||||||
|
|
||||||
static std::span<const unsigned char> getFlashProgramOpcodes(const std::string& key);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -70,41 +70,20 @@ namespace DebugToolDrivers::Wch
|
|||||||
return UsbDevice::getSerialNumber();
|
return UsbDevice::getSerialNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator* WchLinkBase::getRiscVDebugInterface(
|
WchLinkDebugInterface* WchLinkBase::getRiscVDebugInterface(
|
||||||
const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile,
|
const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile,
|
||||||
const Targets::RiscV::RiscVTargetConfig& targetConfig
|
const Targets::RiscV::RiscVTargetConfig& targetConfig
|
||||||
) {
|
) {
|
||||||
using ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator;
|
|
||||||
|
|
||||||
if (!this->wchRiscVTranslator) {
|
if (!this->wchLinkDebugInterface) {
|
||||||
this->wchRiscVTranslator = std::make_unique<DebugTranslator>(
|
this->wchLinkDebugInterface = std::make_unique<WchLinkDebugInterface>(
|
||||||
*(this->wchLinkInterface),
|
this->toolConfig,
|
||||||
this->toolConfig.riscVDebugTranslatorConfig,
|
targetConfig,
|
||||||
targetDescriptionFile,
|
targetDescriptionFile,
|
||||||
targetConfig
|
*(this->wchLinkInterface)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this->wchRiscVTranslator.get();
|
return this->wchLinkDebugInterface.get();
|
||||||
}
|
|
||||||
|
|
||||||
Protocols::WchLink::WchLinkProgrammingInterface* WchLinkBase::getRiscVProgramInterface(
|
|
||||||
const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile,
|
|
||||||
const Targets::RiscV::RiscVTargetConfig& targetConfig
|
|
||||||
) {
|
|
||||||
if (!this->wchLinkProgrammingInterface) {
|
|
||||||
this->wchLinkProgrammingInterface = std::make_unique<Protocols::WchLink::WchLinkProgrammingInterface>(
|
|
||||||
*(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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeviceInfo& WchLinkBase::getDeviceInfo() const {
|
const DeviceInfo& WchLinkBase::getDeviceInfo() const {
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
|
||||||
#include "src/DebugToolDrivers/USB/UsbInterface.hpp"
|
#include "src/DebugToolDrivers/USB/UsbInterface.hpp"
|
||||||
|
|
||||||
|
#include "Protocols/WchLink/WchLinkInterface.hpp"
|
||||||
|
|
||||||
|
#include "WchLinkDebugInterface.hpp"
|
||||||
|
|
||||||
#include "WchLinkToolConfig.hpp"
|
#include "WchLinkToolConfig.hpp"
|
||||||
#include "src/ProjectConfig.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 "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp"
|
||||||
|
|
||||||
#include "WchGeneric.hpp"
|
#include "WchGeneric.hpp"
|
||||||
@@ -43,29 +43,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
|
|
||||||
std::string getSerialNumber() override;
|
std::string getSerialNumber() override;
|
||||||
|
|
||||||
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator* getRiscVDebugInterface(
|
WchLinkDebugInterface* 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(
|
|
||||||
const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile,
|
const Targets::RiscV::TargetDescriptionFile& targetDescriptionFile,
|
||||||
const Targets::RiscV::RiscVTargetConfig& targetConfig
|
const Targets::RiscV::RiscVTargetConfig& targetConfig
|
||||||
) override;
|
) override;
|
||||||
@@ -79,8 +57,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
std::uint8_t wchLinkUsbInterfaceNumber;
|
std::uint8_t wchLinkUsbInterfaceNumber;
|
||||||
std::unique_ptr<Usb::UsbInterface> wchLinkUsbInterface = nullptr;
|
std::unique_ptr<Usb::UsbInterface> wchLinkUsbInterface = nullptr;
|
||||||
std::unique_ptr<Protocols::WchLink::WchLinkInterface> wchLinkInterface = nullptr;
|
std::unique_ptr<Protocols::WchLink::WchLinkInterface> wchLinkInterface = nullptr;
|
||||||
std::unique_ptr<Protocols::WchLink::WchLinkProgrammingInterface> wchLinkProgrammingInterface = nullptr;
|
std::unique_ptr<WchLinkDebugInterface> wchLinkDebugInterface = nullptr;
|
||||||
std::unique_ptr<::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslator> wchRiscVTranslator = nullptr;
|
|
||||||
|
|
||||||
mutable std::optional<DeviceInfo> cachedDeviceInfo = std::nullopt;
|
mutable std::optional<DeviceInfo> cachedDeviceInfo = std::nullopt;
|
||||||
|
|
||||||
|
|||||||
308
src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp
Normal file
308
src/DebugToolDrivers/WCH/WchLinkDebugInterface.cpp
Normal file
@@ -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<WchTargetVariantId>(
|
||||||
|
(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<Targets::TargetMemoryAddressRange>& 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<TargetMemorySize>(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<TargetMemorySize>(std::ceil(
|
||||||
|
static_cast<double>(bufferSize) / static_cast<double>(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<const unsigned char> 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 + "\")"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/DebugToolDrivers/WCH/WchLinkDebugInterface.hpp
Normal file
107
src/DebugToolDrivers/WCH/WchLinkDebugInterface.hpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<Targets::TargetMemoryAddressRange>& 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<WchTargetVariantId> cachedVariantId;
|
||||||
|
std::optional<WchTargetId> cachedTargetId;
|
||||||
|
|
||||||
|
std::span<const unsigned char> flashProgramOpcodes;
|
||||||
|
Targets::TargetMemorySize programmingPacketSize;
|
||||||
|
|
||||||
|
void writeFlashMemory(Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryBufferSpan buffer);
|
||||||
|
void eraseFlashMemory();
|
||||||
|
|
||||||
|
static std::span<const unsigned char> getFlashProgramOpcodes(const std::string& key);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
const auto& toolNode = toolConfig.toolNode;
|
const auto& toolNode = toolConfig.toolNode;
|
||||||
|
|
||||||
if (toolNode["riscVDebugTranslator"]) {
|
if (toolNode["riscVDebugTranslator"]) {
|
||||||
this->riscVDebugTranslatorConfig = Protocols::RiscVDebugSpec::DebugTranslatorConfig{
|
this->riscVDebugTranslatorConfig = ::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslatorConfig{
|
||||||
toolNode["riscVDebugTranslator"]
|
toolNode["riscVDebugTranslator"]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
struct WchLinkToolConfig: public DebugToolConfig
|
struct WchLinkToolConfig: public DebugToolConfig
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Protocols::RiscVDebugSpec::DebugTranslatorConfig riscVDebugTranslatorConfig = {};
|
::DebugToolDrivers::Protocols::RiscVDebugSpec::DebugTranslatorConfig riscVDebugTranslatorConfig = {};
|
||||||
|
|
||||||
explicit WchLinkToolConfig(const DebugToolConfig& toolConfig);
|
explicit WchLinkToolConfig(const DebugToolConfig& toolConfig);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,23 +39,11 @@ namespace Targets::RiscV
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
bool RiscV::supportsDebugTool(DebugTool* debugTool) {
|
bool RiscV::supportsDebugTool(DebugTool* debugTool) {
|
||||||
return
|
return debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig) != nullptr;
|
||||||
debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig) != nullptr
|
|
||||||
&& debugTool->getRiscVProgramInterface(this->targetDescriptionFile, this->targetConfig) != nullptr
|
|
||||||
&& debugTool->getRiscVIdentificationInterface(this->targetDescriptionFile, this->targetConfig) != nullptr
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RiscV::setDebugTool(DebugTool* debugTool) {
|
void RiscV::setDebugTool(DebugTool* debugTool) {
|
||||||
this->riscVDebugInterface = debugTool->getRiscVDebugInterface(this->targetDescriptionFile, this->targetConfig);
|
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() {
|
void RiscV::activate() {
|
||||||
@@ -249,78 +237,6 @@ namespace Targets::RiscV
|
|||||||
static_cast<TargetMemoryAddress>(startAddress + buffer.size()) - 1)
|
static_cast<TargetMemoryAddress>(startAddress + buffer.size()) - 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH
|
|
||||||
&& this->isProgramMemory(
|
|
||||||
addressSpaceDescriptor,
|
|
||||||
memorySegmentDescriptor,
|
|
||||||
startAddress,
|
|
||||||
static_cast<TargetMemorySize>(buffer.size())
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const auto alignmentSize = this->riscVProgramInterface->alignmentSize(
|
|
||||||
addressSpaceDescriptor,
|
|
||||||
memorySegmentDescriptor,
|
|
||||||
startAddress,
|
|
||||||
static_cast<TargetMemorySize>(buffer.size())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (alignmentSize.has_value()) {
|
|
||||||
const auto bufferSize = static_cast<TargetMemorySize>(buffer.size());
|
|
||||||
const auto alignedStartAddress = (startAddress / *alignmentSize) * *alignmentSize;
|
|
||||||
const auto alignedBufferSize = static_cast<TargetMemorySize>(std::ceil(
|
|
||||||
static_cast<double>(bufferSize) / static_cast<double>(*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(
|
return this->riscVDebugInterface->writeMemory(
|
||||||
addressSpaceDescriptor,
|
addressSpaceDescriptor,
|
||||||
memorySegmentDescriptor,
|
memorySegmentDescriptor,
|
||||||
@@ -342,7 +258,7 @@ namespace Targets::RiscV
|
|||||||
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
|
||||||
) {
|
) {
|
||||||
this->riscVProgramInterface->eraseProgramMemory(addressSpaceDescriptor, memorySegmentDescriptor);
|
this->riscVDebugInterface->eraseMemory(addressSpaceDescriptor, memorySegmentDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetExecutionState RiscV::getExecutionState() {
|
TargetExecutionState RiscV::getExecutionState() {
|
||||||
@@ -358,7 +274,6 @@ namespace Targets::RiscV
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RiscV::setProgramCounter(TargetMemoryAddress programCounter) {
|
void RiscV::setProgramCounter(TargetMemoryAddress programCounter) {
|
||||||
// TODO: test this
|
|
||||||
this->riscVDebugInterface->writeCpuRegisters({
|
this->riscVDebugInterface->writeCpuRegisters({
|
||||||
{
|
{
|
||||||
this->pcRegisterDescriptor,
|
this->pcRegisterDescriptor,
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
#include "TargetDescriptionFile.hpp"
|
#include "TargetDescriptionFile.hpp"
|
||||||
|
|
||||||
#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.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
|
namespace Targets::RiscV
|
||||||
{
|
{
|
||||||
@@ -97,8 +95,6 @@ namespace Targets::RiscV
|
|||||||
TargetDescriptionFile targetDescriptionFile;
|
TargetDescriptionFile targetDescriptionFile;
|
||||||
|
|
||||||
DebugToolDrivers::TargetInterfaces::RiscV::RiscVDebugInterface* riscVDebugInterface = nullptr;
|
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
|
* On RISC-V targets, CPU registers are typically only accessible via the debug module (we can't access them
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Targets::RiscV::Wch
|
|||||||
* the variant ID.
|
* the variant ID.
|
||||||
*/
|
*/
|
||||||
const auto variantsById = this->targetDescriptionFile.getVariantsByWchVariantId();
|
const auto variantsById = this->targetDescriptionFile.getVariantsByWchVariantId();
|
||||||
const auto deviceId = this->riscVIdInterface->getDeviceId();
|
const auto deviceId = this->riscVDebugInterface->getDeviceId();
|
||||||
|
|
||||||
const auto variantIt = variantsById.find(deviceId);
|
const auto variantIt = variantsById.find(deviceId);
|
||||||
if (variantIt == variantsById.end()) {
|
if (variantIt == variantsById.end()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user