2023-11-18 22:58:48 +00:00
|
|
|
#include "WchLinkInterface.hpp"
|
|
|
|
|
|
2023-11-21 21:40:40 +00:00
|
|
|
#include <cassert>
|
2024-09-04 00:18:24 +01:00
|
|
|
#include <thread>
|
2023-11-21 21:40:40 +00:00
|
|
|
|
|
|
|
|
#include "Commands/Control/GetDeviceInfo.hpp"
|
|
|
|
|
#include "Commands/Control/AttachTarget.hpp"
|
2023-11-26 00:41:45 +00:00
|
|
|
#include "Commands/Control/DetachTarget.hpp"
|
2024-11-23 20:41:56 +00:00
|
|
|
#include "Commands/Control/PostAttach.hpp"
|
2023-11-21 21:40:40 +00:00
|
|
|
#include "Commands/SetClockSpeed.hpp"
|
|
|
|
|
#include "Commands/DebugModuleInterfaceOperation.hpp"
|
2023-12-09 18:22:32 +00:00
|
|
|
#include "Commands/PreparePartialFlashPageWrite.hpp"
|
2024-11-16 20:05:26 +00:00
|
|
|
#include "Commands/StartProgrammingSession.hpp"
|
|
|
|
|
#include "Commands/EndProgrammingSession.hpp"
|
|
|
|
|
#include "Commands/SetFlashWriteRegion.hpp"
|
|
|
|
|
#include "Commands/StartRamCodeWrite.hpp"
|
|
|
|
|
#include "Commands/EndRamCodeWrite.hpp"
|
|
|
|
|
#include "Commands/WriteFlash.hpp"
|
|
|
|
|
#include "Commands/EraseChip.hpp"
|
2023-11-18 22:58:48 +00:00
|
|
|
|
|
|
|
|
#include "src/Helpers/BiMap.hpp"
|
2023-11-21 21:40:40 +00:00
|
|
|
#include "src/Services/StringService.hpp"
|
2023-11-18 22:58:48 +00:00
|
|
|
|
2023-11-21 21:40:40 +00:00
|
|
|
#include "src/Logger/Logger.hpp"
|
|
|
|
|
|
2023-11-18 22:58:48 +00:00
|
|
|
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
|
|
|
|
|
|
|
|
|
|
namespace DebugToolDrivers::Wch::Protocols::WchLink
|
|
|
|
|
{
|
2024-07-23 21:14:22 +01:00
|
|
|
using namespace ::DebugToolDrivers::Protocols::RiscVDebugSpec;
|
2023-11-18 22:58:48 +00:00
|
|
|
using namespace Exceptions;
|
|
|
|
|
|
2024-07-23 21:14:22 +01:00
|
|
|
using DebugModule::DmiOperation;
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2023-12-02 19:31:20 +00:00
|
|
|
WchLinkInterface::WchLinkInterface(Usb::UsbInterface& usbInterface, Usb::UsbDevice& usbDevice)
|
2023-11-18 22:58:48 +00:00
|
|
|
: usbInterface(usbInterface)
|
2023-12-02 19:31:20 +00:00
|
|
|
, commandEndpointMaxPacketSize(usbDevice.getEndpointMaxPacketSize(WchLinkInterface::USB_COMMAND_ENDPOINT_OUT))
|
|
|
|
|
, dataEndpointMaxPacketSize(usbDevice.getEndpointMaxPacketSize(WchLinkInterface::USB_DATA_ENDPOINT_OUT))
|
2023-11-18 22:58:48 +00:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
DeviceInfo WchLinkInterface::getDeviceInfo() {
|
2024-07-23 21:14:22 +01:00
|
|
|
const auto response = this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo{});
|
2023-11-18 22:58:48 +00:00
|
|
|
if (response.payload.size() < 3) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Cannot construct DeviceInfo response - invalid payload"};
|
2023-11-18 22:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 21:14:22 +01:00
|
|
|
static const auto variantsById = BiMap<std::uint8_t, WchLinkVariant>{
|
2023-11-18 22:58:48 +00:00
|
|
|
{0x01, WchLinkVariant::LINK_CH549},
|
|
|
|
|
{0x02, WchLinkVariant::LINK_E_CH32V307},
|
2023-11-21 21:40:40 +00:00
|
|
|
{0x12, WchLinkVariant::LINK_E_CH32V307},
|
2023-11-18 22:58:48 +00:00
|
|
|
{0x03, WchLinkVariant::LINK_S_CH32V203},
|
2024-07-23 21:14:22 +01:00
|
|
|
};
|
2023-11-18 22:58:48 +00:00
|
|
|
|
2024-10-19 14:22:43 +01:00
|
|
|
return DeviceInfo{
|
2024-07-23 21:14:22 +01:00
|
|
|
WchFirmwareVersion{response.payload[0], response.payload[1]},
|
2023-11-18 22:58:48 +00:00
|
|
|
response.payload.size() >= 4
|
2024-11-16 20:06:55 +00:00
|
|
|
? variantsById.valueAt(response.payload[2])
|
2023-11-18 22:58:48 +00:00
|
|
|
: std::nullopt
|
2024-10-19 14:22:43 +01:00
|
|
|
};
|
2023-11-18 22:58:48 +00:00
|
|
|
}
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2024-07-23 21:14:22 +01:00
|
|
|
void WchLinkInterface::activate() {
|
2023-11-21 21:40:40 +00:00
|
|
|
this->setClockSpeed(WchLinkTargetClockSpeed::CLK_6000_KHZ);
|
|
|
|
|
|
2024-11-23 20:41:56 +00:00
|
|
|
auto response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget{});
|
2023-11-21 21:40:40 +00:00
|
|
|
if (response.payload.size() != 5) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for AttachTarget command"};
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-23 20:41:56 +00:00
|
|
|
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>(
|
2023-11-21 21:40:40 +00:00
|
|
|
(response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8)
|
|
|
|
|
| (response.payload[4])
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WchLinkInterface::deactivate() {
|
2024-07-23 21:14:22 +01:00
|
|
|
const auto response = this->sendCommandAndWaitForResponse(Commands::Control::DetachTarget{});
|
2023-11-26 00:41:45 +00:00
|
|
|
if (response.payload.size() != 1) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for DetachTarget command"};
|
2023-11-26 00:41:45 +00:00
|
|
|
}
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string WchLinkInterface::getDeviceId() {
|
2024-11-23 20:41:56 +00:00
|
|
|
return "0x" + Services::StringService::toHex(this->cachedVariantId.value());
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 21:14:22 +01:00
|
|
|
DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister(DebugModule::RegisterAddress address) {
|
|
|
|
|
using DebugModule::DmiOperationStatus;
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2024-09-04 00:18:24 +01:00
|
|
|
auto attempt = std::uint8_t{0};
|
|
|
|
|
while (attempt < WchLinkInterface::DMI_OP_MAX_RETRY) {
|
|
|
|
|
if (attempt > 0) {
|
|
|
|
|
std::this_thread::sleep_for(this->dmiOpRetryDelay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto response = this->sendCommandAndWaitForResponse(
|
|
|
|
|
Commands::DebugModuleInterfaceOperation{DmiOperation::READ, address}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.operationStatus == DmiOperationStatus::SUCCESS) {
|
|
|
|
|
return response.value;
|
|
|
|
|
}
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2024-09-04 00:18:24 +01:00
|
|
|
if (response.operationStatus == DmiOperationStatus::FAILED) {
|
|
|
|
|
throw Exceptions::DeviceCommunicationFailure{"DMI operation failed"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Busy response...
|
|
|
|
|
++attempt;
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-04 00:18:24 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"DMI operation timed out"};
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WchLinkInterface::writeDebugModuleRegister(
|
2024-07-23 21:14:22 +01:00
|
|
|
DebugModule::RegisterAddress address,
|
|
|
|
|
DebugModule::RegisterValue value
|
2023-11-21 21:40:40 +00:00
|
|
|
) {
|
2024-07-23 21:14:22 +01:00
|
|
|
using DebugModule::DmiOperationStatus;
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2024-09-04 00:18:24 +01:00
|
|
|
auto attempt = std::uint8_t{0};
|
|
|
|
|
while (attempt < WchLinkInterface::DMI_OP_MAX_RETRY) {
|
|
|
|
|
if (attempt > 0) {
|
|
|
|
|
std::this_thread::sleep_for(this->dmiOpRetryDelay);
|
|
|
|
|
}
|
2023-11-21 21:40:40 +00:00
|
|
|
|
2024-09-04 00:18:24 +01:00
|
|
|
const auto response = this->sendCommandAndWaitForResponse(
|
|
|
|
|
Commands::DebugModuleInterfaceOperation{DmiOperation::WRITE, address, value}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.operationStatus == DmiOperationStatus::SUCCESS) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.operationStatus == DmiOperationStatus::FAILED) {
|
|
|
|
|
throw Exceptions::DeviceCommunicationFailure{"DMI operation failed"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Busy response...
|
|
|
|
|
++attempt;
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
2024-09-04 00:18:24 +01:00
|
|
|
|
|
|
|
|
throw Exceptions::DeviceCommunicationFailure{"DMI operation timed out"};
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-16 20:05:26 +00:00
|
|
|
void WchLinkInterface::writePartialPage(
|
2023-12-09 18:22:32 +00:00
|
|
|
Targets::TargetMemoryAddress startAddress,
|
2024-11-16 20:05:26 +00:00
|
|
|
Targets::TargetMemoryBufferSpan buffer
|
2023-12-09 18:22:32 +00:00
|
|
|
) {
|
2024-11-16 20:05:26 +00:00
|
|
|
constexpr auto packetSize = std::uint8_t{64};
|
2023-12-09 18:22:32 +00:00
|
|
|
const auto bufferSize = static_cast<Targets::TargetMemorySize>(buffer.size());
|
|
|
|
|
const auto packetsRequired = static_cast<std::uint32_t>(
|
|
|
|
|
std::ceil(static_cast<float>(bufferSize) / static_cast<float>(packetSize))
|
|
|
|
|
);
|
|
|
|
|
|
2024-07-23 21:14:22 +01:00
|
|
|
for (auto i = std::uint32_t{0}; i < packetsRequired; ++i) {
|
2024-11-16 20:05:26 +00:00
|
|
|
const auto segmentSize = static_cast<std::uint8_t>(
|
|
|
|
|
std::min(
|
|
|
|
|
static_cast<std::uint8_t>(bufferSize - (i * packetSize)),
|
|
|
|
|
packetSize
|
|
|
|
|
)
|
|
|
|
|
);
|
2023-12-09 18:22:32 +00:00
|
|
|
const auto response = this->sendCommandAndWaitForResponse(
|
2024-07-23 21:14:22 +01:00
|
|
|
Commands::PreparePartialFlashPageWrite{startAddress + (packetSize * i), segmentSize}
|
2023-12-09 18:22:32 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.payload.size() != 1) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{
|
2023-12-09 18:22:32 +00:00
|
|
|
"Unexpected response payload size for PreparePartialFlashPageWrite command"
|
2024-07-23 21:14:22 +01:00
|
|
|
};
|
2023-12-09 18:22:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->usbInterface.writeBulk(
|
|
|
|
|
WchLinkInterface::USB_DATA_ENDPOINT_OUT,
|
2024-11-16 20:05:26 +00:00
|
|
|
buffer.subspan(packetSize * i, segmentSize),
|
|
|
|
|
this->dataEndpointMaxPacketSize
|
2023-12-09 18:22:32 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const auto rawResponse = this->usbInterface.readBulk(WchLinkInterface::USB_DATA_ENDPOINT_IN);
|
|
|
|
|
if (rawResponse.size() != 4) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response size for partial flash page write"};
|
2023-12-09 18:22:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2024-11-16 20:05:26 +00:00
|
|
|
* I have no idea what any of these bytes mean. I've not been able to find any documentation for
|
2024-07-23 21:14:22 +01:00
|
|
|
* this. All I know is that these values indicate a successful write.
|
2023-12-09 18:22:32 +00:00
|
|
|
*/
|
2024-11-16 20:05:26 +00:00
|
|
|
if (rawResponse[0] != 0x41 || rawResponse[1] != 0x01 || rawResponse[2] != 0x01 || rawResponse[3] != 0x02) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Partial flash page write failed"};
|
2023-12-09 18:22:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-16 20:05:26 +00:00
|
|
|
void WchLinkInterface::writeFullPage(
|
|
|
|
|
Targets::TargetMemoryAddress startAddress,
|
|
|
|
|
Targets::TargetMemoryBufferSpan buffer,
|
|
|
|
|
Targets::TargetMemorySize pageSize,
|
|
|
|
|
std::span<const unsigned char> flashProgramOpcodes
|
|
|
|
|
) {
|
|
|
|
|
assert((buffer.size() % pageSize) == 0);
|
|
|
|
|
|
|
|
|
|
const auto bufferSize = static_cast<Targets::TargetMemorySize>(buffer.size());
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We don't issue the StartProgrammingSession command here, as it seems to result in a failure when writing a
|
|
|
|
|
* flash page. We get a 0x05 in rawResponse[3], but I have no idea why.
|
|
|
|
|
*/
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::SetFlashWriteRegion{startAddress, bufferSize});
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::StartRamCodeWrite{});
|
|
|
|
|
|
|
|
|
|
this->usbInterface.writeBulk(
|
|
|
|
|
WchLinkInterface::USB_DATA_ENDPOINT_OUT,
|
|
|
|
|
flashProgramOpcodes,
|
|
|
|
|
this->dataEndpointMaxPacketSize
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::EndRamCodeWrite{});
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::WriteFlash{});
|
|
|
|
|
|
|
|
|
|
auto bytesWritten = Targets::TargetMemorySize{0};
|
|
|
|
|
while (bytesWritten < bufferSize) {
|
|
|
|
|
const auto length = std::min(bufferSize - bytesWritten, pageSize);
|
|
|
|
|
|
|
|
|
|
this->usbInterface.writeBulk(
|
|
|
|
|
WchLinkInterface::USB_DATA_ENDPOINT_OUT,
|
|
|
|
|
buffer.subspan(bytesWritten, length),
|
|
|
|
|
this->dataEndpointMaxPacketSize
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const auto rawResponse = this->usbInterface.readBulk(WchLinkInterface::USB_DATA_ENDPOINT_IN);
|
|
|
|
|
if (rawResponse.size() != 4) {
|
|
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response size for flash page write"};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rawResponse[3] != 0x02 && rawResponse[3] != 0x04) {
|
|
|
|
|
throw Exceptions::DeviceCommunicationFailure{
|
|
|
|
|
"Flash page write failed - unexpected response (0x"
|
|
|
|
|
+ Services::StringService::toHex(rawResponse[3]) + ")"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytesWritten += length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::EndProgrammingSession{});
|
|
|
|
|
|
|
|
|
|
this->deactivate();
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo{});
|
|
|
|
|
this->activate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WchLinkInterface::eraseChip() {
|
|
|
|
|
this->sendCommandAndWaitForResponse(Commands::EraseChip{});
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 21:40:40 +00:00
|
|
|
void WchLinkInterface::setClockSpeed(WchLinkTargetClockSpeed speed) {
|
2024-07-23 21:14:22 +01:00
|
|
|
const auto speedIdsBySpeed = BiMap<WchLinkTargetClockSpeed, std::uint8_t>{
|
2023-11-21 21:40:40 +00:00
|
|
|
{WchLinkTargetClockSpeed::CLK_6000_KHZ, 0x01},
|
|
|
|
|
{WchLinkTargetClockSpeed::CLK_4000_KHZ, 0x02},
|
|
|
|
|
{WchLinkTargetClockSpeed::CLK_400_KHZ, 0x03},
|
2024-07-23 21:14:22 +01:00
|
|
|
};
|
2023-11-21 21:40:40 +00:00
|
|
|
|
|
|
|
|
const auto response = this->sendCommandAndWaitForResponse(
|
2024-11-23 20:41:56 +00:00
|
|
|
Commands::SetClockSpeed{this->cachedTargetId.value_or(0x01), speedIdsBySpeed.at(speed)}
|
2023-11-21 21:40:40 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.payload.size() != 1) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response payload size for SetClockSpeed command"};
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.payload[0] != 0x01) {
|
2024-07-23 21:14:22 +01:00
|
|
|
throw Exceptions::DeviceCommunicationFailure{"Unexpected response for SetClockSpeed command"};
|
2023-11-21 21:40:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-18 22:58:48 +00:00
|
|
|
}
|