#include "DebugTranslator.hpp" #include #include #include #include #include #include "Registers/CpuRegisterNumbers.hpp" #include "DebugModule/Registers/RegisterAddresses.hpp" #include "DebugModule/Registers/RegisterAccessControlField.hpp" #include "DebugModule/Registers/MemoryAccessControlField.hpp" #include "src/Exceptions/Exception.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "src/TargetController/Exceptions/DeviceInitializationFailure.hpp" #include "src/TargetController/Exceptions/TargetOperationFailure.hpp" #include "src/Logger/Logger.hpp" namespace DebugToolDrivers::Protocols::RiscVDebugSpec { using Registers::DebugControlStatusRegister; using DebugModule::Registers::RegisterAddress; using DebugModule::Registers::ControlRegister; using DebugModule::Registers::StatusRegister; using DebugModule::Registers::AbstractControlStatusRegister; using DebugModule::Registers::AbstractCommandRegister; using Registers::CpuRegisterNumber; using namespace ::Targets::RiscV; using ::Targets::TargetExecutionState; using ::Targets::TargetMemoryAddress; using ::Targets::TargetMemoryAddressRange; using ::Targets::TargetMemorySize; using ::Targets::TargetMemoryBuffer; using ::Targets::TargetStackPointer; using ::Targets::TargetAddressSpaceDescriptor; using ::Targets::TargetMemorySegmentDescriptor; using ::Targets::TargetRegisterDescriptors; using ::Targets::TargetRegisterDescriptorAndValuePairs; DebugTranslator::DebugTranslator( DebugTransportModuleInterface& dtmInterface, const TargetDescriptionFile& targetDescriptionFile, const RiscVTargetConfig& targetConfig ) : dtmInterface(dtmInterface) , targetDescriptionFile(targetDescriptionFile) , targetConfig(targetConfig) {} void DebugTranslator::init() { // No pre-activation initialisation required. return; } void DebugTranslator::activate() { this->dtmInterface.activate(); this->hartIndices = this->discoverHartIndices(); if (this->hartIndices.empty()) { throw Exceptions::TargetOperationFailure{"Failed to discover any RISC-V harts"}; } Logger::debug("Discovered RISC-V harts: " + std::to_string(this->hartIndices.size())); /* * We only support debugging a single RISC-V hart, for now. * * If we discover more than one, we select the first one and ensure that this is explicitly communicated to the * user. */ if (this->hartIndices.size() > 1) { Logger::warning("Bloom only supports debugging a single RISC-V hart - selecting first available"); } this->selectedHartIndex = this->hartIndices.front(); Logger::info("Selected RISC-V hart index: " + std::to_string(this->selectedHartIndex)); /* * Disabling the debug module before enabling it will clear any state from a previous debug session that * wasn't terminated properly. */ this->disableDebugModule(); this->enableDebugModule(); this->stop(); this->reset(); auto debugControlStatusRegister = this->readDebugControlStatusRegister(); debugControlStatusRegister.breakUMode = true; debugControlStatusRegister.breakSMode = true; debugControlStatusRegister.breakMMode = true; this->writeDebugControlStatusRegister(debugControlStatusRegister); } void DebugTranslator::deactivate() { this->disableDebugModule(); this->dtmInterface.deactivate(); } TargetExecutionState DebugTranslator::getExecutionState() { return this->readDebugModuleStatusRegister().anyRunning ? TargetExecutionState::RUNNING : TargetExecutionState::STOPPED; } void DebugTranslator::stop() { auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; controlRegister.haltRequest = true; this->writeDebugModuleControlRegister(controlRegister); constexpr auto maxAttempts = 10; auto statusRegister = this->readDebugModuleStatusRegister(); for (auto attempts = 1; !statusRegister.allHalted && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); statusRegister = this->readDebugModuleStatusRegister(); } controlRegister.haltRequest = false; this->writeDebugModuleControlRegister(controlRegister); if (!statusRegister.allHalted) { throw Exceptions::Exception{"Target took too long to halt selected harts"}; } } void DebugTranslator::run() { auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; controlRegister.resumeRequest = true; this->writeDebugModuleControlRegister(controlRegister); constexpr auto maxAttempts = 10; auto statusRegister = this->readDebugModuleStatusRegister(); for (auto attempts = 1; !statusRegister.allResumeAcknowledge && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); statusRegister = this->readDebugModuleStatusRegister(); } controlRegister.resumeRequest = false; this->writeDebugModuleControlRegister(controlRegister); if (!statusRegister.allResumeAcknowledge) { throw Exceptions::Exception{"Target took too long to acknowledge resume request"}; } } void DebugTranslator::step() { auto debugControlStatusRegister = this->readDebugControlStatusRegister(); debugControlStatusRegister.step = true; this->writeDebugControlStatusRegister(debugControlStatusRegister); auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; controlRegister.resumeRequest = true; this->writeDebugModuleControlRegister(controlRegister); controlRegister.resumeRequest = false; this->writeDebugModuleControlRegister(controlRegister); debugControlStatusRegister.step = false; this->writeDebugControlStatusRegister(debugControlStatusRegister); } void DebugTranslator::reset() { auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; controlRegister.setResetHaltRequest = true; controlRegister.haltRequest = true; controlRegister.ndmReset = true; this->writeDebugModuleControlRegister(controlRegister); controlRegister.ndmReset = false; this->writeDebugModuleControlRegister(controlRegister); constexpr auto maxAttempts = 10; auto statusRegister = this->readDebugModuleStatusRegister(); for (auto attempts = 1; !statusRegister.allHaveReset && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); statusRegister = this->readDebugModuleStatusRegister(); } controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; controlRegister.clearResetHaltRequest = true; controlRegister.acknowledgeHaveReset = true; this->writeDebugModuleControlRegister(controlRegister); if (!statusRegister.allHaveReset) { throw Exceptions::Exception{"Target took too long to reset"}; } } void DebugTranslator::setSoftwareBreakpoint(TargetMemoryAddress address) { } void DebugTranslator::clearSoftwareBreakpoint(TargetMemoryAddress address) { } void DebugTranslator::setHardwareBreakpoint(TargetMemoryAddress address) { } void DebugTranslator::clearHardwareBreakpoint(TargetMemoryAddress address) { } void DebugTranslator::clearAllBreakpoints() { } TargetRegisterDescriptorAndValuePairs DebugTranslator::readCpuRegisters( const TargetRegisterDescriptors& descriptors ) { auto output = TargetRegisterDescriptorAndValuePairs{}; for (const auto& descriptor : descriptors) { const auto registerValue = this->readCpuRegister(static_cast(descriptor->startAddress)); output.emplace_back( *descriptor, TargetMemoryBuffer{ static_cast(registerValue >> 24), static_cast(registerValue >> 16), static_cast(registerValue >> 8), static_cast(registerValue), } ); } return output; } void DebugTranslator::writeCpuRegisters(const TargetRegisterDescriptorAndValuePairs& registers) { for (const auto& [descriptor, value] : registers) { assert((value.size() * 8) > std::numeric_limits::digits); auto registerValue = RegisterValue{0}; for (const auto& registerByte : value) { registerValue = (registerValue << 8) | registerByte; } this->writeCpuRegister(static_cast(descriptor.startAddress), registerValue); } } TargetMemoryBuffer DebugTranslator::readMemory( const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor, TargetMemoryAddress startAddress, TargetMemorySize bytes, const std::set& excludedAddressRanges ) { using DebugModule::Registers::MemoryAccessControlField; // TODO: excluded addresses const auto pageSize = 4; if ((startAddress % pageSize) != 0 || (bytes % pageSize) != 0) { // Alignment required const auto alignedStartAddress = this->alignMemoryAddress(startAddress, pageSize); const auto alignedBytes = this->alignMemorySize(bytes + (startAddress - alignedStartAddress), pageSize); const auto memoryBuffer = this->readMemory( addressSpaceDescriptor, memorySegmentDescriptor, alignedStartAddress, alignedBytes, excludedAddressRanges ); const auto offset = memoryBuffer.begin() + (startAddress - alignedStartAddress); return TargetMemoryBuffer{offset, offset + bytes}; } auto output = TargetMemoryBuffer{}; output.reserve(bytes); /* * We only need to set the address once. No need to update it as we use the post-increment function to * increment the address. See MemoryAccessControlField::postIncrement */ this->dtmInterface.writeDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_1, startAddress); constexpr auto command = AbstractCommandRegister{ MemoryAccessControlField{ false, true, MemoryAccessControlField::MemorySize::SIZE_32, false }.value(), AbstractCommandRegister::CommandType::MEMORY_ACCESS }; for (auto address = startAddress; address <= (startAddress + bytes - 1); address += 4) { this->executeAbstractCommand(command); const auto data = this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0); output.emplace_back(static_cast(data)); output.emplace_back(static_cast(data >> 8)); output.emplace_back(static_cast(data >> 16)); output.emplace_back(static_cast(data >> 24)); } return output; } void DebugTranslator::writeMemory( const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor, TargetMemoryAddress startAddress, const TargetMemoryBuffer& buffer ) { using DebugModule::Registers::MemoryAccessControlField; constexpr auto alignTo = TargetMemorySize{4}; const auto bytes = static_cast(buffer.size()); if ((startAddress % alignTo) != 0 || (bytes % alignTo) != 0) { /* * Alignment required * * To align the write operation, we read the front and back offset bytes and use them to construct an * aligned buffer. */ const auto alignedStartAddress = this->alignMemoryAddress(startAddress, alignTo); const auto alignedBytes = this->alignMemorySize(bytes + (startAddress - alignedStartAddress), alignTo); assert(alignedBytes > bytes); auto alignedBuffer = (alignedStartAddress < startAddress) ? this->readMemory( addressSpaceDescriptor, memorySegmentDescriptor, alignedStartAddress, (startAddress - alignedStartAddress) ) : TargetMemoryBuffer{}; alignedBuffer.reserve(alignedBytes); // Read the offset bytes required to align the buffer size const auto dataBack = this->readMemory( addressSpaceDescriptor, memorySegmentDescriptor, startAddress + bytes, alignedBytes - bytes - (startAddress - alignedStartAddress) ); alignedBuffer.insert(alignedBuffer.end(), dataBack.begin(), dataBack.end()); return this->writeMemory( addressSpaceDescriptor, memorySegmentDescriptor, alignedStartAddress, alignedBuffer ); } this->dtmInterface.writeDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_1, startAddress); constexpr auto command = AbstractCommandRegister{ MemoryAccessControlField{ true, true, MemoryAccessControlField::MemorySize::SIZE_32, false }.value(), AbstractCommandRegister::CommandType::MEMORY_ACCESS }; for (TargetMemoryAddress offset = 0; offset < buffer.size(); offset += 4) { this->dtmInterface.writeDebugModuleRegister( RegisterAddress::ABSTRACT_DATA_0, static_cast( (buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) | (buffer[offset + 1] << 8) | (buffer[offset]) ) ); this->executeAbstractCommand(command); } } std::vector DebugTranslator::discoverHartIndices() { auto hartIndices = std::vector{}; /* * We can obtain the maximum hart index by setting all of the hartsel bits in the control register and then * read the value back. */ auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = 0xFFFFF; this->writeDebugModuleControlRegister(controlRegister); const auto maxHartIndex = this->readDebugModuleControlRegister().selectedHartIndex; for (auto hartIndex = DebugModule::HartIndex{0}; hartIndex <= maxHartIndex; ++hartIndex) { /* * We can't just assume that everything between 0 and the maximum hart index are valid hart indices. We * have to test each index until we find one that is non-existent. */ auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = hartIndex; this->writeDebugModuleControlRegister(controlRegister); /* * It's worth noting that some RISC-V targets **do not** set the non-existent flags. I'm not sure why. * Maybe hartsel has been hardwired to 0 on targets that only support a single hart, preventing the * selection of non-existent harts. * * Relying on the maximum hart index seems to be all we can do in this case. */ if (this->readDebugModuleStatusRegister().anyNonExistent) { break; } hartIndices.emplace_back(hartIndex); } return hartIndices; } ControlRegister DebugTranslator::readDebugModuleControlRegister() { return ControlRegister{this->dtmInterface.readDebugModuleRegister(RegisterAddress::CONTROL_REGISTER)}; } StatusRegister DebugTranslator::readDebugModuleStatusRegister() { return StatusRegister{this->dtmInterface.readDebugModuleRegister(RegisterAddress::STATUS_REGISTER)}; } AbstractControlStatusRegister DebugTranslator::readDebugModuleAbstractControlStatusRegister() { return AbstractControlStatusRegister{ this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_CONTROL_STATUS_REGISTER) }; } DebugControlStatusRegister DebugTranslator::readDebugControlStatusRegister() { return DebugControlStatusRegister{ this->readCpuRegister(static_cast(CpuRegisterNumber::DEBUG_CONTROL_STATUS_REGISTER)) }; } void DebugTranslator::enableDebugModule() { auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = true; controlRegister.selectedHartIndex = this->selectedHartIndex; this->writeDebugModuleControlRegister(controlRegister); constexpr auto maxAttempts = 10; controlRegister = this->readDebugModuleControlRegister(); for (auto attempts = 1; !controlRegister.debugModuleActive && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); controlRegister = this->readDebugModuleControlRegister(); } if (!controlRegister.debugModuleActive) { throw Exceptions::Exception{"Took too long to enable debug module"}; } } void DebugTranslator::disableDebugModule() { auto controlRegister = ControlRegister{}; controlRegister.debugModuleActive = false; controlRegister.selectedHartIndex = this->selectedHartIndex; this->writeDebugModuleControlRegister(controlRegister); constexpr auto maxAttempts = 10; controlRegister = this->readDebugModuleControlRegister(); for (auto attempts = 1; controlRegister.debugModuleActive && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); controlRegister = this->readDebugModuleControlRegister(); } if (controlRegister.debugModuleActive) { throw Exceptions::Exception{"Took too long to disable debug module"}; } } RegisterValue DebugTranslator::readCpuRegister(RegisterNumber number) { using DebugModule::Registers::RegisterAccessControlField; this->executeAbstractCommand(AbstractCommandRegister{ RegisterAccessControlField{ number, false, true, false, false, RegisterAccessControlField::RegisterSize::SIZE_32 }.value(), AbstractCommandRegister::CommandType::REGISTER_ACCESS }); return this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0); } void DebugTranslator::writeCpuRegister(RegisterNumber number, RegisterValue value) { using DebugModule::Registers::RegisterAccessControlField; this->dtmInterface.writeDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0, value); this->executeAbstractCommand(AbstractCommandRegister{ RegisterAccessControlField{ number, true, true, false, false, RegisterAccessControlField::RegisterSize::SIZE_32 }.value(), AbstractCommandRegister::CommandType::REGISTER_ACCESS }); } void DebugTranslator::writeDebugModuleControlRegister(const ControlRegister& controlRegister) { this->dtmInterface.writeDebugModuleRegister(RegisterAddress::CONTROL_REGISTER, controlRegister.value()); } void DebugTranslator::writeDebugControlStatusRegister(const DebugControlStatusRegister& controlRegister) { this->writeCpuRegister( static_cast(CpuRegisterNumber::DEBUG_CONTROL_STATUS_REGISTER), controlRegister.value() ); } void DebugTranslator::executeAbstractCommand( const DebugModule::Registers::AbstractCommandRegister& abstractCommandRegister ) { this->dtmInterface.writeDebugModuleRegister( RegisterAddress::ABSTRACT_COMMAND_REGISTER, abstractCommandRegister.value() ); auto abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister(); if (abstractStatusRegister.commandError != AbstractControlStatusRegister::CommandError::NONE) { throw Exceptions::Exception{ "Failed to execute abstract command - error: " + Services::StringService::toHex(abstractStatusRegister.commandError) }; } constexpr auto maxAttempts = 10; for (auto attempts = 1; abstractStatusRegister.busy && attempts <= maxAttempts; ++attempts) { std::this_thread::sleep_for(std::chrono::microseconds{10}); abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister(); } if (abstractStatusRegister.busy) { throw Exceptions::Exception{"Abstract command took too long to execute"}; } } TargetMemoryAddress DebugTranslator::alignMemoryAddress(TargetMemoryAddress address, TargetMemoryAddress alignTo) { return static_cast( std::floor(static_cast(address) / static_cast(alignTo)) ) * alignTo; } TargetMemorySize DebugTranslator::alignMemorySize(TargetMemorySize size, TargetMemorySize alignTo) { return static_cast( std::ceil(static_cast(size) / static_cast(alignTo)) ) * alignTo; } }