Refactored WCH-Link/RISC-V implementation to accommodate SW breakpoints and reduce complexity

This commit is contained in:
Nav
2024-11-24 19:32:00 +00:00
parent dc87b92fb2
commit 7c647caa67
21 changed files with 500 additions and 560 deletions

View File

@@ -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, 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{});
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<WchTargetVariantId>(
(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, 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"};
}
}
}

View File

@@ -8,8 +8,6 @@
#include <utility>
#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<const unsigned char> 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<WchTargetVariantId> cachedVariantId;
std::optional<WchTargetId> cachedTargetId;
void setClockSpeed(WchLinkTargetClockSpeed speed);
template <class CommandType>
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<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};
};
}

View File

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

View File

@@ -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);
};
}