Initial pass at a RiscVDebugInterface and implementation (for WCH-Link debug tools)

This commit is contained in:
Nav
2023-11-21 21:40:40 +00:00
parent 516892f7eb
commit 826da3e921
10 changed files with 382 additions and 7 deletions

View File

@@ -0,0 +1,80 @@
#pragma once
#include <cstdint>
#include "src/Targets/RiscV/DebugModule/DebugModule.hpp"
#include "src/Targets/RiscV/TargetParameters.hpp"
namespace DebugToolDrivers::TargetInterfaces::RiscV
{
class RiscVDebugInterface
{
public:
RiscVDebugInterface() = default;
virtual ~RiscVDebugInterface() = default;
RiscVDebugInterface(const RiscVDebugInterface& other) = default;
RiscVDebugInterface(RiscVDebugInterface&& other) = default;
RiscVDebugInterface& operator = (const RiscVDebugInterface& other) = default;
RiscVDebugInterface& operator = (RiscVDebugInterface&& other) = default;
/**
* 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.
*
* @param targetParameters
* Parameters for the RISC-V target. These can be ignored if a particular implementation does not require
* any target parameters for activation.
*/
virtual void activate(const Targets::RiscV::TargetParameters& targetParameters) = 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 retrieve the RISC-V target ID in string form.
*
* @return
* The target ID, in the form of a string.
*/
virtual std::string getDeviceId() = 0;
/**
* Should read the value of a debug module register.
*
* @param address
* The address of the debug module register to read.
*
* @return
* The register value.
*/
virtual Targets::RiscV::DebugModule::RegisterValue readDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address
) = 0;
/**
* Should write a value to a debug module register.
*
* @param address
* The address of the debug module to update.
*
* @param value
* The value to write.
*/
virtual void writeDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address,
Targets::RiscV::DebugModule::RegisterValue value
) = 0;
};
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands::Control
{
class AttachTarget: public Command<std::array<unsigned char, 1>>
{
public:
AttachTarget()
: Command(0x0d)
{
this->payload = {
0x02
};
}
};
}

View File

@@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <optional>
#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp"
#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Responses/DebugModuleInterfaceOperationResponse.hpp"
#include "src/Targets/RiscV/DebugModule/DebugModule.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands
{
class DebugModuleInterfaceOperation: public Command<std::array<unsigned char, 6>>
{
public:
using ExpectedResponseType = Responses::DebugModuleInterfaceOperationResponse;
DebugModuleInterfaceOperation(
Targets::RiscV::DebugModule::DmiOperation operation,
Targets::RiscV::DebugModule::RegisterAddress address,
std::optional<Targets::RiscV::DebugModule::RegisterValue> value = std::nullopt
)
: Command(0x08)
{
if (!value.has_value()) {
value = 0x00;
}
this->payload = {
address,
static_cast<unsigned char>(*value >> 24),
static_cast<unsigned char>(*value >> 16),
static_cast<unsigned char>(*value >> 8),
static_cast<unsigned char>(*value),
static_cast<unsigned char>(operation),
};
}
};
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include "src/DebugToolDrivers/WCH/Protocols/WchLink/Commands/Command.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink::Commands
{
class SetClockSpeed: public Command<std::array<unsigned char, 2>>
{
public:
SetClockSpeed(std::uint8_t targetGroupId, std::uint8_t speedId)
: Command(0x0c)
{
this->payload = {
targetGroupId,
speedId,
};
}
};
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <vector>
#include "src/Targets/RiscV/DebugModule/DebugModule.hpp"
#include "src/Helpers/BiMap.hpp"
#include "src/Services/StringService.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink::Responses
{
class DebugModuleInterfaceOperationResponse
{
public:
Targets::RiscV::DebugModule::DmiOperationStatus operationStatus;
Targets::RiscV::DebugModule::RegisterAddress address;
Targets::RiscV::DebugModule::RegisterValue value;
explicit DebugModuleInterfaceOperationResponse(const std::vector<unsigned char>& payload)
{
if (payload.size() != 6) {
throw Exceptions::DeviceCommunicationFailure(
"Unexpected response payload size for DMI operation command"
);
}
const auto status = payload[5];
if (
status != static_cast<unsigned char>(Targets::RiscV::DebugModule::DmiOperationStatus::SUCCESS)
&& status != static_cast<unsigned char>(Targets::RiscV::DebugModule::DmiOperationStatus::FAILED)
&& status != static_cast<unsigned char>(Targets::RiscV::DebugModule::DmiOperationStatus::BUSY)
) {
throw Exceptions::DeviceCommunicationFailure(
"Unknown DMI operation status returned: 0x" + Services::StringService::toHex(status)
);
}
this->operationStatus = static_cast<Targets::RiscV::DebugModule::DmiOperationStatus>(status);
this->address = payload[0];
this->value = static_cast<Targets::RiscV::DebugModule::RegisterValue>(
(payload[1] << 24) | (payload[2] << 16) | (payload[3] << 8) | (payload[4])
);
}
};
}

View File

@@ -1,10 +1,18 @@
#include "WchLinkInterface.hpp"
#include <memory>
#include "src/Helpers/BiMap.hpp"
#include <cassert>
#include "Commands/Control/GetDeviceInfo.hpp"
#include "Commands/Control/AttachTarget.hpp"
#include "Commands/SetClockSpeed.hpp"
#include "Commands/DebugModuleInterfaceOperation.hpp"
#include "src/Helpers/BiMap.hpp"
#include "src/Services/StringService.hpp"
#include "src/Logger/Logger.hpp"
#include "src/Targets/RiscV/DebugModule/Registers/ControlRegister.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
@@ -12,14 +20,14 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
{
using namespace Exceptions;
using Targets::RiscV::DebugModule::DmiOperation;
WchLinkInterface::WchLinkInterface(Usb::UsbInterface& usbInterface)
: usbInterface(usbInterface)
{}
DeviceInfo WchLinkInterface::getDeviceInfo() {
const auto response = this->sendCommandAndWaitForResponse(
Commands::Control::GetDeviceInfo()
);
const auto response = this->sendCommandAndWaitForResponse(Commands::Control::GetDeviceInfo());
if (response.payload.size() < 3) {
throw Exceptions::DeviceCommunicationFailure("Cannot construct DeviceInfo response - invalid payload");
@@ -28,6 +36,7 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
static const auto variantsById = BiMap<std::uint8_t, WchLinkVariant>({
{0x01, WchLinkVariant::LINK_CH549},
{0x02, WchLinkVariant::LINK_E_CH32V307},
{0x12, WchLinkVariant::LINK_E_CH32V307},
{0x03, WchLinkVariant::LINK_S_CH32V203},
});
@@ -38,4 +47,79 @@ namespace DebugToolDrivers::Wch::Protocols::WchLink
: std::nullopt
);
}
void WchLinkInterface::activate(const Targets::RiscV::TargetParameters& targetParameters) {
this->setClockSpeed(WchLinkTargetClockSpeed::CLK_6000_KHZ);
const auto response = this->sendCommandAndWaitForResponse(Commands::Control::AttachTarget());
if (response.payload.size() != 5) {
throw Exceptions::DeviceCommunicationFailure("Unexpected response payload size for AttachTarget command");
}
this->cachedTargetId = static_cast<WchTargetId>(
(response.payload[1] << 24) | (response.payload[2] << 16) | (response.payload[3] << 8)
| (response.payload[4])
);
this->cachedTargetGroupId = response.payload[0];
}
void WchLinkInterface::deactivate() {
// TODO: implement this
}
std::string WchLinkInterface::getDeviceId() {
return "0x" + Services::StringService::toHex(this->cachedTargetId.value());
}
Targets::RiscV::DebugModule::RegisterValue WchLinkInterface::readDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address
) {
using Targets::RiscV::DebugModule::DmiOperationStatus ;
const auto response = this->sendCommandAndWaitForResponse(
Commands::DebugModuleInterfaceOperation(DmiOperation::READ, address)
);
if (response.operationStatus != DmiOperationStatus::SUCCESS) {
throw Exceptions::DeviceCommunicationFailure("DMI operation failed");
}
return response.value;
}
void WchLinkInterface::writeDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address,
Targets::RiscV::DebugModule::RegisterValue value
) {
using Targets::RiscV::DebugModule::DmiOperationStatus ;
const auto response = this->sendCommandAndWaitForResponse(
Commands::DebugModuleInterfaceOperation(DmiOperation::WRITE, address, value)
);
if (response.operationStatus != DmiOperationStatus::SUCCESS) {
throw Exceptions::DeviceCommunicationFailure("DMI operation failed");
}
}
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->cachedTargetGroupId.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

@@ -4,29 +4,67 @@
#include <optional>
#include <vector>
#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp"
#include "src/DebugToolDrivers/USB/UsbInterface.hpp"
#include "src/DebugToolDrivers/WCH/WchGeneric.hpp"
#include "Commands/Command.hpp"
#include "src/Targets/RiscV/DebugModule/DebugModule.hpp"
#include "src/DebugToolDrivers/WCH/DeviceInfo.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink
{
class WchLinkInterface
/**
* The WchLinkInterface implements the WCH-Link protocol.
*/
class WchLinkInterface: public TargetInterfaces::RiscV::RiscVDebugInterface
{
public:
explicit WchLinkInterface(Usb::UsbInterface& usbInterface);
DeviceInfo getDeviceInfo();
void activate(const Targets::RiscV::TargetParameters& targetParameters) override;
void deactivate() override;
std::string getDeviceId() override;
Targets::RiscV::DebugModule::RegisterValue readDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address
) override;
void writeDebugModuleRegister(
Targets::RiscV::DebugModule::RegisterAddress address,
Targets::RiscV::DebugModule::RegisterValue value
) override;
private:
static constexpr std::uint8_t USB_ENDPOINT_IN = 0x81;
static constexpr std::uint8_t USB_ENDPOINT_OUT = 0x01;
Usb::UsbInterface& usbInterface;
/**
* The 'target activation' command returns a payload of 5 bytes.
*
* The last 4 bytes hold the WCH RISC-V target ID. Given that the 'target activation' command appears to be
* the only way to obtain the target ID, we cache it via WchLinkInterface::cachedTargetId and return the
* cached value in WchLinkInterface::getTargetId().
*
* As for the first byte in the payload, I'm not really sure what it is. It appears to be some kind of
* identifier for groups of WCH RISC-V targets. It's unclear. All I know is that it has some significance, as
* it's expected in the payload of some other commands, such as the command to set clock speed. For this
* reason, we have to keep hold of it via WchLinkInterface::cachedTargetGroupId.
*/
std::optional<WchTargetId> cachedTargetId;
std::optional<std::uint8_t> cachedTargetGroupId;
void setClockSpeed(WchLinkTargetClockSpeed speed);
template <class CommandType>
auto sendCommandAndWaitForResponse(const CommandType& command) {
this->usbInterface.writeBulk(WchLinkInterface::USB_ENDPOINT_OUT, command.getRawCommand());

View File

@@ -4,6 +4,8 @@
namespace DebugToolDrivers::Wch
{
using WchTargetId = std::uint32_t;
enum class WchLinkVariant: std::uint8_t
{
LINK_CH549,
@@ -11,4 +13,11 @@ namespace DebugToolDrivers::Wch
LINK_S_CH32V203,
UNKNOWN,
};
enum class WchLinkTargetClockSpeed: std::uint8_t
{
CLK_400_KHZ,
CLK_4000_KHZ,
CLK_6000_KHZ,
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <vector>
#include <set>
#include <optional>
namespace Targets::RiscV::DebugModule
{
using RegisterAddress = std::uint8_t;
using RegisterValue = std::uint32_t;
using HartIndex = std::uint32_t;
enum class DmiOperation: std::uint8_t
{
IGNORE = 0x00,
READ = 0x01,
WRITE = 0x02,
};
enum class DmiOperationStatus: std::uint8_t
{
SUCCESS = 0x00,
FAILED = 0x02,
BUSY = 0x03,
};
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
#include "src/Targets/TargetMemory.hpp"
namespace Targets::RiscV
{
struct TargetParameters
{};
}