First pass at RISC-V hardware breakpoints (Trigger module)

This commit is contained in:
Nav
2024-10-06 17:54:08 +01:00
parent 7fc1145d4b
commit ecd0f5b054
11 changed files with 470 additions and 9 deletions

View File

@@ -11,6 +11,11 @@
#include "DebugModule/Registers/RegisterAccessControlField.hpp"
#include "DebugModule/Registers/MemoryAccessControlField.hpp"
#include "TriggerModule/Registers/TriggerSelect.hpp"
#include "TriggerModule/Registers/TriggerInfo.hpp"
#include "TriggerModule/Registers/TriggerData1.hpp"
#include "TriggerModule/Registers/MatchControl.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Exceptions/InvalidConfig.hpp"
#include "src/TargetController/Exceptions/DeviceInitializationFailure.hpp"
@@ -90,6 +95,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
this->stop();
this->reset();
this->triggerDescriptorsByIndex = this->discoverTriggers();
auto debugControlStatusRegister = this->readDebugControlStatusRegister();
debugControlStatusRegister.breakUMode = true;
@@ -213,23 +219,76 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
}
void DebugTranslator::setSoftwareBreakpoint(TargetMemoryAddress address) {
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->triggerDescriptorsByIndex.size());
}
void DebugTranslator::setHardwareBreakpoint(TargetMemoryAddress address) {
using TriggerModule::TriggerType;
const auto triggerDescriptorOpt = this->getAvailableTrigger();
if (!triggerDescriptorOpt.has_value()) {
throw Exceptions::Exception{"Insufficient resources - no available trigger"};
}
const auto& triggerDescriptor = triggerDescriptorOpt->get();
Logger::debug("Installing hardware BP at address " + Services::StringService::toHex(address) + " with index " + std::to_string(triggerDescriptor.index));
if (triggerDescriptor.supportedTypes.contains(TriggerType::MATCH_CONTROL)) {
using TriggerModule::Registers::MatchControl;
this->writeCpuRegister(
CpuRegisterNumber::TRIGGER_SELECT,
TriggerModule::Registers::TriggerSelect{triggerDescriptor.index}.value()
);
auto matchControlRegister = MatchControl{};
matchControlRegister.execute = true;
matchControlRegister.enabledInUserMode = true;
matchControlRegister.enabledInSupervisorMode = true;
matchControlRegister.enabledInMachineMode = true;
matchControlRegister.action = TriggerModule::TriggerAction::ENTER_DEBUG_MODE;
matchControlRegister.accessSize = MatchControl::AccessSize::ANY;
matchControlRegister.compareValueType = MatchControl::CompareValueType::ADDRESS;
this->writeCpuRegister(CpuRegisterNumber::TRIGGER_DATA_1, matchControlRegister.value());
this->writeCpuRegister(CpuRegisterNumber::TRIGGER_DATA_2, address);
this->allocatedTriggerIndices.emplace(triggerDescriptor.index);
this->triggerIndicesByBreakpointAddress.emplace(address, triggerDescriptor.index);
return;
}
throw Exceptions::Exception{"Unsupported trigger"};
}
void DebugTranslator::clearHardwareBreakpoint(TargetMemoryAddress address) {
const auto triggerIndexIt = this->triggerIndicesByBreakpointAddress.find(address);
if (triggerIndexIt == this->triggerIndicesByBreakpointAddress.end()) {
throw Exceptions::Exception{"Unknown hardware breakpoint"};
}
const auto& triggerDescriptor = this->triggerDescriptorsByIndex.at(triggerIndexIt->second);
this->clearTrigger(triggerDescriptor);
this->triggerIndicesByBreakpointAddress.erase(address);
this->allocatedTriggerIndices.erase(triggerDescriptor.index);
}
void DebugTranslator::clearAllBreakpoints() {
for (const auto [triggerIndex, triggerDescriptor] : this->triggerDescriptorsByIndex) {
this->clearTrigger(triggerDescriptor);
}
this->triggerIndicesByBreakpointAddress.clear();
this->allocatedTriggerIndices.clear();
}
TargetRegisterDescriptorAndValuePairs DebugTranslator::readCpuRegisters(
@@ -445,6 +504,63 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
return hartIndices;
}
std::unordered_map<
TriggerModule::TriggerIndex,
TriggerModule::TriggerDescriptor
> DebugTranslator::discoverTriggers() {
auto output = std::unordered_map<TriggerModule::TriggerIndex, TriggerModule::TriggerDescriptor>{};
constexpr auto MAX_TRIGGER_INDEX = 10;
for (auto triggerIndex = TriggerModule::TriggerIndex{0}; triggerIndex <= MAX_TRIGGER_INDEX; ++triggerIndex) {
const auto selectRegValue = TriggerModule::Registers::TriggerSelect{triggerIndex}.value();
const auto writeSelectError = this->tryWriteCpuRegister(CpuRegisterNumber::TRIGGER_SELECT, selectRegValue);
if (writeSelectError == DebugModule::AbstractCommandError::EXCEPTION) {
break;
}
if (writeSelectError != DebugModule::AbstractCommandError::NONE) {
throw Exceptions::Exception{
"Failed to write to TRIGGER_SELECT register - abstract command error: 0x"
+ Services::StringService::toHex(writeSelectError)
};
}
if (this->readCpuRegister(CpuRegisterNumber::TRIGGER_SELECT) != selectRegValue) {
break;
}
const auto infoReg = TriggerModule::Registers::TriggerInfo{
this->readCpuRegister(CpuRegisterNumber::TRIGGER_INFO)
};
if (infoReg.info == 0x01) {
// Trigger doesn't exist
break;
}
auto supportedTypes = infoReg.getSupportedTriggerTypes();
if (supportedTypes.empty()) {
// The trigger info register has no trigger type info. Try the data1 register.
const auto data1Reg = TriggerModule::Registers::TriggerData1{
this->readCpuRegister(CpuRegisterNumber::TRIGGER_DATA_1)
};
const auto triggerType = data1Reg.getType();
if (!triggerType.has_value()) {
// Trigger data1 register also lacks type info. Assume the trigger doesn't exist
break;
}
supportedTypes.insert(*triggerType);
}
output.emplace(triggerIndex, TriggerModule::TriggerDescriptor{triggerIndex, supportedTypes});
}
return output;
}
ControlRegister DebugTranslator::readDebugModuleControlRegister() {
return ControlRegister{this->dtmInterface.readDebugModuleRegister(RegisterAddress::CONTROL_REGISTER)};
}
@@ -650,4 +766,38 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
std::ceil(static_cast<double>(size) / static_cast<double>(alignTo))
) * alignTo;
}
std::optional<
std::reference_wrapper<const TriggerModule::TriggerDescriptor>
> DebugTranslator::getAvailableTrigger() {
for (const auto& [index, descriptor] : this->triggerDescriptorsByIndex) {
if (this->allocatedTriggerIndices.contains(index)) {
continue;
}
return descriptor;
}
return std::nullopt;
}
void DebugTranslator::clearTrigger(const TriggerModule::TriggerDescriptor& triggerDescriptor) {
using TriggerModule::TriggerType;
Logger::debug("Clearing trigger " + std::to_string(triggerDescriptor.index)); // TODO: keep this, but reword it
if (triggerDescriptor.supportedTypes.contains(TriggerType::MATCH_CONTROL)) {
using TriggerModule::Registers::MatchControl;
this->writeCpuRegister(
CpuRegisterNumber::TRIGGER_SELECT,
TriggerModule::Registers::TriggerSelect{triggerDescriptor.index}.value()
);
this->writeCpuRegister(CpuRegisterNumber::TRIGGER_DATA_1, MatchControl{}.value());
return;
}
throw Exceptions::Exception{"Unsupported trigger"};
}
}

View File

@@ -2,6 +2,11 @@
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <optional>
#include <functional>
#include "src/DebugToolDrivers/TargetInterfaces/RiscV/RiscVDebugInterface.hpp"
@@ -20,6 +25,8 @@
#include "DebugModule/Registers/AbstractControlStatusRegister.hpp"
#include "DebugModule/Registers/AbstractCommandRegister.hpp"
#include "TriggerModule/TriggerModule.hpp"
#include "TriggerModule/TriggerDescriptor.hpp"
#include "src/Helpers/Expected.hpp"
@@ -53,6 +60,7 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
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 clearAllBreakpoints() override;
@@ -85,7 +93,12 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
std::vector<DebugModule::HartIndex> hartIndices;
DebugModule::HartIndex selectedHartIndex = 0;
std::unordered_map<TriggerModule::TriggerIndex, TriggerModule::TriggerDescriptor> triggerDescriptorsByIndex;
std::unordered_set<TriggerModule::TriggerIndex> allocatedTriggerIndices;
std::unordered_map<Targets::TargetMemoryAddress, TriggerModule::TriggerIndex> triggerIndicesByBreakpointAddress;
std::vector<DebugModule::HartIndex> discoverHartIndices();
std::unordered_map<TriggerModule::TriggerIndex, TriggerModule::TriggerDescriptor> discoverTriggers();
DebugModule::Registers::ControlRegister readDebugModuleControlRegister();
DebugModule::Registers::StatusRegister readDebugModuleStatusRegister();
@@ -120,5 +133,8 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
Targets::TargetMemoryAddress alignTo
);
Targets::TargetMemorySize alignMemorySize(Targets::TargetMemorySize size, Targets::TargetMemorySize alignTo);
std::optional<std::reference_wrapper<const TriggerModule::TriggerDescriptor>> getAvailableTrigger();
void clearTrigger(const TriggerModule::TriggerDescriptor& triggerDescriptor);
};
}

View File

@@ -7,5 +7,10 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec::Registers
enum class CpuRegisterNumber: RegisterNumber
{
DEBUG_CONTROL_STATUS_REGISTER = 0x07b0,
TRIGGER_SELECT = 0x07a0,
TRIGGER_DATA_1 = 0x07a1,
TRIGGER_DATA_2 = 0x07a2,
TRIGGER_DATA_3 = 0x07a3,
TRIGGER_INFO = 0x07a4,
};
}

View File

@@ -0,0 +1,125 @@
#pragma once
#include <cstdint>
#include <optional>
#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/TriggerModule/TriggerModule.hpp"
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule::Registers
{
struct MatchControl
{
enum class MatchMode: std::uint8_t
{
EQUAL = 0x00,
TOP_BITS = 0x01,
GREATER_THAN = 0x02,
LESS_THAN = 0x03,
MASK_LOW = 0x04,
MASK_HIGH = 0x05,
NOT_EQUAL = 0x08,
NOT_TOP_BITS = 0x09,
NOT_MASK_LOW = 0x0C,
NOT_MASK_HIGH = 0x0D,
};
enum class AccessSize: std::uint8_t
{
ANY = 0x00,
SIZE_8 = 0x01,
SIZE_16 = 0x02,
SIZE_32 = 0x03,
};
enum class CompareValueType: std::uint8_t
{
ADDRESS = 0x00,
DATA = 0x01,
};
bool load:1 = false;
bool store:1 = false;
bool execute:1 = false;
bool enabledInUserMode:1 = false;
bool enabledInSupervisorMode:1 = false;
bool enabledInMachineMode:1 = false;
MatchMode matchMode:4 = MatchMode::EQUAL;
bool chain:1 = false; // TODO: Consider making this an enum
TriggerAction action:4 = TriggerAction::ENTER_DEBUG_MODE;
AccessSize accessSize = AccessSize::ANY;
bool timing:1 = false; // TODO: Consider making this an enum
CompareValueType compareValueType:1 = CompareValueType::ADDRESS;
bool hit:1 = false;
MatchControl() = default;
constexpr explicit MatchControl(
bool load,
bool store,
bool execute,
bool enabledInUserMode,
bool enabledInSupervisorMode,
bool enabledInMachineMode,
MatchMode matchMode,
bool chain,
TriggerAction action,
AccessSize accessSize,
bool timing,
CompareValueType compareValueType,
bool hit
)
: load(load)
, store(store)
, execute(execute)
, enabledInUserMode(enabledInUserMode)
, enabledInSupervisorMode(enabledInSupervisorMode)
, enabledInMachineMode(enabledInMachineMode)
, matchMode(matchMode)
, chain(chain)
, action(action)
, accessSize(accessSize)
, timing(timing)
, compareValueType(compareValueType)
, hit(hit)
{}
constexpr explicit MatchControl(RegisterValue registerValue)
: load(static_cast<bool>(registerValue & 0x01))
, store(static_cast<bool>(registerValue & (0x01 << 1)))
, execute(static_cast<bool>(registerValue & (0x01 << 2)))
, enabledInUserMode(static_cast<bool>(registerValue & (0x01 << 3)))
, enabledInSupervisorMode(static_cast<bool>(registerValue & (0x01 << 4)))
, enabledInMachineMode(static_cast<bool>(registerValue & (0x01 << 6)))
, matchMode(static_cast<MatchMode>((registerValue >> 7) & 0x0F))
, chain(static_cast<bool>(registerValue & (0x01 << 11)))
, action(static_cast<TriggerAction>((registerValue >> 12) & 0x0F))
, accessSize(
static_cast<AccessSize>(
(((registerValue >> 21) & 0x03) << 2) | ((registerValue >> 16) & 0x03)
)
)
, timing(static_cast<bool>(registerValue & (0x01 << 18)))
, compareValueType(static_cast<CompareValueType>((registerValue >> 19) & 0x01))
, hit(static_cast<bool>(registerValue & (0x01 << 20)))
{}
[[nodiscard]] constexpr RegisterValue value() const {
return RegisterValue{0}
| static_cast<RegisterValue>(this->load)
| static_cast<RegisterValue>(this->store) << 1
| static_cast<RegisterValue>(this->execute) << 2
| static_cast<RegisterValue>(this->enabledInUserMode) << 3
| static_cast<RegisterValue>(this->enabledInSupervisorMode) << 4
| static_cast<RegisterValue>(this->enabledInMachineMode) << 6
| static_cast<RegisterValue>(this->matchMode) << 7
| static_cast<RegisterValue>(this->chain) << 11
| static_cast<RegisterValue>(this->action) << 12
| (static_cast<RegisterValue>(this->accessSize) & 0x03) << 16
| static_cast<RegisterValue>(this->timing) << 18
| static_cast<RegisterValue>(this->compareValueType) << 19
| static_cast<RegisterValue>(this->hit) << 20
| (static_cast<RegisterValue>(this->accessSize) >> 2) << 21
;
}
};
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <optional>
#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/TriggerModule/TriggerModule.hpp"
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule::Registers
{
struct TriggerData1
{
std::uint32_t data:27;
bool debugModeOnly:1;
std::uint8_t type:4;
TriggerData1() = default;
constexpr explicit TriggerData1(RegisterValue registerValue)
: data(registerValue & 0x07FFFFFF)
, debugModeOnly(static_cast<bool>(registerValue & (0x01 << 27)))
, type(static_cast<std::uint8_t>(registerValue >> 28) & 0x0F)
{}
[[nodiscard]] constexpr RegisterValue value() const {
return RegisterValue{0}
| static_cast<RegisterValue>(this->data)
| static_cast<RegisterValue>(this->debugModeOnly) << 27
| static_cast<RegisterValue>(this->type) << 28
;
}
std::optional<TriggerType> getType() const {
return (this->type >= 0x01 && this->type <= 0x07)
? std::optional{static_cast<TriggerType>(this->type)}
: std::nullopt;
}
};
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include <cstdint>
#include <set>
#include <array>
#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/TriggerModule/TriggerModule.hpp"
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule::Registers
{
struct TriggerInfo
{
std::uint16_t info;
std::uint8_t version;
constexpr explicit TriggerInfo(RegisterValue registerValue)
: info(registerValue & 0xFFFF)
, version(static_cast<std::uint8_t>(registerValue >> 24))
{}
[[nodiscard]] constexpr RegisterValue value() const {
return RegisterValue{0}
| static_cast<RegisterValue>(this->info)
| static_cast<RegisterValue>(this->version) << 24
;
}
std::set<TriggerType> getSupportedTriggerTypes() const {
auto output = std::set<TriggerType>{};
static constexpr auto types = std::to_array<TriggerType>({
TriggerType::LEGACY,
TriggerType::MATCH_CONTROL,
TriggerType::INSTRUCTION_COUNT,
TriggerType::INTERRUPT_TRIGGER,
TriggerType::EXCEPTION_TRIGGER,
TriggerType::MATCH_CONTROL_TYPE_6,
TriggerType::EXTERNAL,
});
for (const auto& type : types) {
if (this->info & (0x01 << static_cast<std::uint8_t>(type))) {
output.insert(type);
}
}
return output;
}
};
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include "src/DebugToolDrivers/Protocols/RiscVDebugSpec/TriggerModule/TriggerModule.hpp"
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule::Registers
{
struct TriggerSelect
{
TriggerIndex index;
constexpr explicit TriggerSelect(TriggerIndex index)
: index(index)
{}
[[nodiscard]] constexpr RegisterValue value() const {
return static_cast<RegisterValue>(this->index);
}
};
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
#include <set>
#include "TriggerModule.hpp"
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule
{
struct TriggerDescriptor
{
TriggerIndex index;
std::set<TriggerType> supportedTypes;
TriggerDescriptor(TriggerIndex index, const std::set<TriggerType>& supportedTypes)
: index(index)
, supportedTypes(supportedTypes)
{}
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
namespace DebugToolDrivers::Protocols::RiscVDebugSpec::TriggerModule
{
using RegisterValue = std::uint32_t;
using TriggerIndex = std::uint32_t;
enum class TriggerType: std::uint8_t
{
NONE = 0x00,
LEGACY = 0x01,
MATCH_CONTROL = 0x02,
INSTRUCTION_COUNT = 0x03,
INTERRUPT_TRIGGER = 0x04,
EXCEPTION_TRIGGER = 0x05,
MATCH_CONTROL_TYPE_6 = 0x06,
EXTERNAL = 0x07,
DISABLED = 0x0F,
};
enum class TriggerAction: std::uint8_t
{
ENTER_DEBUG_MODE = 0x01,
};
}

View File

@@ -31,6 +31,7 @@ namespace DebugToolDrivers::TargetInterfaces::RiscV
virtual void setSoftwareBreakpoint(Targets::TargetMemoryAddress address) = 0;
virtual void clearSoftwareBreakpoint(Targets::TargetMemoryAddress address) = 0;
virtual std::uint16_t getHardwareBreakpointCount() = 0;
virtual void setHardwareBreakpoint(Targets::TargetMemoryAddress address) = 0;
virtual void clearHardwareBreakpoint(Targets::TargetMemoryAddress address) = 0;
virtual void clearAllBreakpoints() = 0;

View File

@@ -70,10 +70,14 @@ namespace Targets::RiscV
}
void RiscV::deactivate() {
if (this->getExecutionState() != TargetExecutionState::RUNNING) {
this->run();
// TODO: Is this "tidy-up" code better placed in the TC? Review after v1.1.0.
if (this->getExecutionState() != TargetExecutionState::STOPPED) {
this->stop();
}
this->clearAllBreakpoints();
this->run();
this->riscVDebugInterface->deactivate();
}
@@ -92,7 +96,11 @@ namespace Targets::RiscV
this->targetDescriptionFile.targetPadDescriptorsByKey(),
this->targetDescriptionFile.targetPinoutDescriptorsByKey(),
this->targetDescriptionFile.targetVariantDescriptorsByKey(),
{} // TODO: populate this
BreakpointResources{
this->riscVDebugInterface->getHardwareBreakpointCount(),
std::nullopt,
static_cast<std::uint16_t>(this->targetConfig.reserveSteppingBreakpoint ? 1 : 0)
}
};
// Copy the RISC-V CPU register address space and peripheral descriptor
@@ -126,23 +134,23 @@ namespace Targets::RiscV
}
void RiscV::setSoftwareBreakpoint(TargetMemoryAddress address) {
throw Exceptions::Exception{"TARGET - SW breakpoints not supported"};
}
void RiscV::removeSoftwareBreakpoint(TargetMemoryAddress address) {
throw Exceptions::Exception{"TARGET - SW breakpoints not supported"};
}
void RiscV::setHardwareBreakpoint(TargetMemoryAddress address) {
this->riscVDebugInterface->setHardwareBreakpoint(address);
}
void RiscV::removeHardwareBreakpoint(TargetMemoryAddress address) {
this->riscVDebugInterface->clearHardwareBreakpoint(address);
}
void RiscV::clearAllBreakpoints() {
this->riscVDebugInterface->clearAllBreakpoints();
}
TargetRegisterDescriptorAndValuePairs RiscV::readRegisters(const TargetRegisterDescriptors& descriptors) {