Handle mapped program memory segment aliasing properly, on WCH RISC-V targets
- Added `program_segment_key` target config param, to allow the user to specify the desired program memory segment - Added the ability to resolve the currently aliased segment, by means of probing the mapped segment - Added program counter transformation, when the mapped segment is aliasing a foreign segment - Other bites of tidying
This commit is contained in:
@@ -12,7 +12,7 @@ namespace DebugServer::Gdb::RiscVGdb
|
|||||||
RiscVGdbTargetDescriptor::RiscVGdbTargetDescriptor(const Targets::TargetDescriptor& targetDescriptor)
|
RiscVGdbTargetDescriptor::RiscVGdbTargetDescriptor(const Targets::TargetDescriptor& targetDescriptor)
|
||||||
: systemAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("system"))
|
: systemAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("system"))
|
||||||
, cpuAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("debug_module"))
|
, cpuAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("debug_module"))
|
||||||
, programMemorySegmentDescriptor(this->systemAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory"))
|
, programMemorySegmentDescriptor(this->systemAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program"))
|
||||||
, gpRegistersMemorySegmentDescriptor(this->cpuAddressSpaceDescriptor.getMemorySegmentDescriptor("gp_registers"))
|
, gpRegistersMemorySegmentDescriptor(this->cpuAddressSpaceDescriptor.getMemorySegmentDescriptor("gp_registers"))
|
||||||
, cpuGpPeripheralDescriptor(targetDescriptor.getPeripheralDescriptor("cpu"))
|
, cpuGpPeripheralDescriptor(targetDescriptor.getPeripheralDescriptor("cpu"))
|
||||||
, cpuGpRegisterGroupDescriptor(this->cpuGpPeripheralDescriptor.getRegisterGroupDescriptor("gpr"))
|
, cpuGpRegisterGroupDescriptor(this->cpuGpPeripheralDescriptor.getRegisterGroupDescriptor("gpr"))
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "src/Exceptions/InternalFatalErrorException.hpp"
|
#include "src/Exceptions/InternalFatalErrorException.hpp"
|
||||||
#include "src/TargetController/Exceptions/TargetFailure.hpp"
|
#include "src/TargetController/Exceptions/TargetFailure.hpp"
|
||||||
#include "src/TargetController/Exceptions/TargetOperationFailure.hpp"
|
#include "src/TargetController/Exceptions/TargetOperationFailure.hpp"
|
||||||
|
#include "src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp"
|
||||||
|
|
||||||
#include "src/Logger/Logger.hpp"
|
#include "src/Logger/Logger.hpp"
|
||||||
|
|
||||||
@@ -963,7 +964,17 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
output.reserve(bytes);
|
output.reserve(bytes);
|
||||||
|
|
||||||
for (auto address = startAddress; address <= (startAddress + bytes - 1); address += 4) {
|
for (auto address = startAddress; address <= (startAddress + bytes - 1); address += 4) {
|
||||||
this->executeAbstractCommand(command);
|
const auto commandError = this->tryExecuteAbstractCommand(command);
|
||||||
|
if (commandError != AbstractCommandError::NONE) {
|
||||||
|
if (commandError == AbstractCommandError::EXCEPTION) {
|
||||||
|
throw Exceptions::IllegalMemoryAccess{};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exceptions::TargetOperationFailure{
|
||||||
|
"Failed to read memory via abstract command - error: 0x"
|
||||||
|
+ Services::StringService::toHex(static_cast<std::uint8_t>(commandError))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const auto data = this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0);
|
const auto data = this->dtmInterface.readDebugModuleRegister(RegisterAddress::ABSTRACT_DATA_0);
|
||||||
output.emplace_back(static_cast<unsigned char>(data));
|
output.emplace_back(static_cast<unsigned char>(data));
|
||||||
@@ -1005,7 +1016,17 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this->executeAbstractCommand(command);
|
const auto commandError = this->tryExecuteAbstractCommand(command);
|
||||||
|
if (commandError != AbstractCommandError::NONE) {
|
||||||
|
if (commandError == AbstractCommandError::EXCEPTION) {
|
||||||
|
throw Exceptions::IllegalMemoryAccess{};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exceptions::TargetOperationFailure{
|
||||||
|
"Failed to write memory via abstract command - error: 0x"
|
||||||
|
+ Services::StringService::toHex(static_cast<std::uint8_t>(commandError))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,7 +1062,22 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this->writeProgramBuffer(programOpcodes);
|
this->writeProgramBuffer(programOpcodes);
|
||||||
this->writeCpuRegister(CpuRegisterNumber::GPR_X8, startAddress, {.postExecute = true});
|
|
||||||
|
auto commandError = this->tryWriteCpuRegister(
|
||||||
|
CpuRegisterNumber::GPR_X8,
|
||||||
|
startAddress,
|
||||||
|
{.postExecute = true}
|
||||||
|
);
|
||||||
|
if (commandError != AbstractCommandError::NONE) {
|
||||||
|
if (commandError == AbstractCommandError::EXCEPTION) {
|
||||||
|
throw Exceptions::IllegalMemoryAccess{};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exceptions::TargetOperationFailure{
|
||||||
|
"Program buffer execution failed - abstract command error: 0x"
|
||||||
|
+ Services::StringService::toHex(commandError)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
auto output = Targets::TargetMemoryBuffer{};
|
auto output = Targets::TargetMemoryBuffer{};
|
||||||
output.reserve(bytes);
|
output.reserve(bytes);
|
||||||
@@ -1101,13 +1137,15 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
output.emplace_back(static_cast<unsigned char>(word >> 24));
|
output.emplace_back(static_cast<unsigned char>(word >> 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister();
|
commandError = this->readAndClearAbstractCommandError();
|
||||||
if (abstractStatusRegister.commandError != AbstractCommandError::NONE) {
|
if (commandError != AbstractCommandError::NONE) {
|
||||||
this->clearAbstractCommandError();
|
if (commandError == AbstractCommandError::EXCEPTION) {
|
||||||
|
throw Exceptions::IllegalMemoryAccess{};
|
||||||
|
}
|
||||||
|
|
||||||
throw Exceptions::TargetOperationFailure{
|
throw Exceptions::TargetOperationFailure{
|
||||||
"Program buffer execution failed - abstract command error: 0x"
|
"Program buffer execution failed - abstract command error: 0x"
|
||||||
+ Services::StringService::toHex(abstractStatusRegister.commandError)
|
+ Services::StringService::toHex(commandError)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1122,11 +1160,10 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|
||||||
} catch (const Exceptions::Exception& exception) {
|
} catch (const Exceptions::Exception&) {
|
||||||
preservedX8Register.restoreOnce();
|
preservedX8Register.restoreOnce();
|
||||||
preservedX9Register.restoreOnce();
|
preservedX9Register.restoreOnce();
|
||||||
|
throw;
|
||||||
throw exception;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1201,24 +1238,25 @@ namespace DebugToolDrivers::Protocols::RiscVDebugSpec
|
|||||||
AbstractCommandAutoExecuteRegister{}.value()
|
AbstractCommandAutoExecuteRegister{}.value()
|
||||||
);
|
);
|
||||||
|
|
||||||
const auto abstractStatusRegister = this->readDebugModuleAbstractControlStatusRegister();
|
const auto commandError = this->readAndClearAbstractCommandError();
|
||||||
if (abstractStatusRegister.commandError != AbstractCommandError::NONE) {
|
if (commandError != AbstractCommandError::NONE) {
|
||||||
this->clearAbstractCommandError();
|
if (commandError == AbstractCommandError::EXCEPTION) {
|
||||||
|
throw Exceptions::IllegalMemoryAccess{};
|
||||||
|
}
|
||||||
|
|
||||||
throw Exceptions::TargetOperationFailure{
|
throw Exceptions::TargetOperationFailure{
|
||||||
"Program buffer execution failed - abstract command error: 0x"
|
"Program buffer execution failed - abstract command error: 0x"
|
||||||
+ Services::StringService::toHex(abstractStatusRegister.commandError)
|
+ Services::StringService::toHex(commandError)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
preservedX8Register.restore();
|
preservedX8Register.restore();
|
||||||
preservedX9Register.restore();
|
preservedX9Register.restore();
|
||||||
|
|
||||||
} catch (const Exceptions::Exception& exception) {
|
} catch (const Exceptions::Exception&) {
|
||||||
preservedX8Register.restoreOnce();
|
preservedX8Register.restoreOnce();
|
||||||
preservedX9Register.restoreOnce();
|
preservedX9Register.restoreOnce();
|
||||||
|
throw;
|
||||||
throw exception;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
, programSegmentDescriptor(
|
, programSegmentDescriptor(
|
||||||
this->targetDescriptionFile.getSystemAddressSpaceDescriptor().getMemorySegmentDescriptor(
|
this->targetDescriptionFile.getSystemAddressSpaceDescriptor().getMemorySegmentDescriptor("main_program")
|
||||||
"internal_program_memory"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
, flashProgramOpcodes(
|
, flashProgramOpcodes(
|
||||||
WchLinkDebugInterface::getFlashProgramOpcodes(
|
WchLinkDebugInterface::getFlashProgramOpcodes(
|
||||||
@@ -105,6 +103,9 @@ namespace DebugToolDrivers::Wch
|
|||||||
* In addition to sending the post-attach command, we have to send another attach command, because the target
|
* 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
|
* 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.
|
* command will ensure that we have a valid target variant ID.
|
||||||
|
*
|
||||||
|
* TODO: Add a property to the target's TDF, to determine whether the post-attach is required, instead of
|
||||||
|
* hardcoding target IDs here. This can be done after v2.0.0.
|
||||||
*/
|
*/
|
||||||
if (this->cachedTargetId == 0x09) {
|
if (this->cachedTargetId == 0x09) {
|
||||||
this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::PostAttach{});
|
this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::PostAttach{});
|
||||||
@@ -236,9 +237,28 @@ namespace DebugToolDrivers::Wch
|
|||||||
* smaller than 64 bytes, such as when we're inserting software breakpoints.
|
* smaller than 64 bytes, such as when we're inserting software breakpoints.
|
||||||
*/
|
*/
|
||||||
const auto bufferSize = static_cast<TargetMemorySize>(buffer.size());
|
const auto bufferSize = static_cast<TargetMemorySize>(buffer.size());
|
||||||
|
const auto alignmentSize = this->programmingBlockSize;
|
||||||
|
const auto alignedStartAddress = (startAddress / alignmentSize) * alignmentSize;
|
||||||
|
const auto alignedBufferSize = static_cast<TargetMemorySize>(std::ceil(
|
||||||
|
static_cast<double>(bufferSize) / static_cast<double>(alignmentSize)
|
||||||
|
) * alignmentSize);
|
||||||
|
const auto alignmentRequired = alignedStartAddress != startAddress || alignedBufferSize != bufferSize;
|
||||||
|
|
||||||
if (bufferSize <= WchLinkInterface::MAX_PARTIAL_BLOCK_WRITE_SIZE) {
|
if (
|
||||||
|
bufferSize <= WchLinkInterface::MAX_PARTIAL_BLOCK_WRITE_SIZE
|
||||||
|
|| (
|
||||||
|
alignmentRequired
|
||||||
|
&& !memorySegmentDescriptor.addressRange.contains(
|
||||||
|
TargetMemoryAddressRange{
|
||||||
|
alignedStartAddress,
|
||||||
|
alignedStartAddress + alignedBufferSize - 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
using namespace ::DebugToolDrivers::Protocols::RiscVDebugSpec;
|
using namespace ::DebugToolDrivers::Protocols::RiscVDebugSpec;
|
||||||
|
Logger::debug("Using partial block write command");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WCH-Link tools seem to make use of the target's program buffer to service the partial block write
|
* WCH-Link tools seem to make use of the target's program buffer to service the partial block write
|
||||||
* command.
|
* command.
|
||||||
@@ -261,28 +281,7 @@ namespace DebugToolDrivers::Wch
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto alignmentSize = this->programmingBlockSize;
|
if (alignmentRequired) {
|
||||||
const auto alignedStartAddress = (startAddress / alignmentSize) * alignmentSize;
|
|
||||||
const auto alignedBufferSize = static_cast<TargetMemorySize>(std::ceil(
|
|
||||||
static_cast<double>(bufferSize) / static_cast<double>(alignmentSize)
|
|
||||||
) * alignmentSize);
|
|
||||||
|
|
||||||
if (alignedStartAddress != startAddress || alignedBufferSize != bufferSize) {
|
|
||||||
if (
|
|
||||||
!memorySegmentDescriptor.addressRange.contains(
|
|
||||||
TargetMemoryAddressRange{
|
|
||||||
alignedStartAddress,
|
|
||||||
alignedStartAddress + alignedBufferSize - 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
/*
|
|
||||||
* TODO: The aligned address range exceeds the bounds of the memory segment. I'm not sure what to
|
|
||||||
* do here. We could just ignore it...I don't think it will cause much of an issue, for now.
|
|
||||||
* Review (after v2.0.0, maybe?).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
auto alignedBuffer = (alignedStartAddress < startAddress)
|
auto alignedBuffer = (alignedStartAddress < startAddress)
|
||||||
? this->readMemory(
|
? this->readMemory(
|
||||||
addressSpaceDescriptor,
|
addressSpaceDescriptor,
|
||||||
@@ -322,6 +321,9 @@ namespace DebugToolDrivers::Wch
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger::debug(
|
||||||
|
"Using full block write command (block size: " + std::to_string(this->programmingBlockSize) + ")"
|
||||||
|
);
|
||||||
this->wchLinkInterface.writeFlashFullBlocks(
|
this->wchLinkInterface.writeFlashFullBlocks(
|
||||||
startAddress,
|
startAddress,
|
||||||
buffer,
|
buffer,
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/TargetDescriptionFile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/TargetDescriptionFile.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/IsaDescriptor.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/IsaDescriptor.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/WchRiscV.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/WchRiscV.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/WchRiscVTargetConfig.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/TargetDescriptionFile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RiscV/Wch/TargetDescriptionFile.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
18
src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp
Normal file
18
src/Targets/RiscV/Exceptions/IllegalMemoryAccess.hpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "src/TargetController/Exceptions/TargetOperationFailure.hpp"
|
||||||
|
|
||||||
|
namespace Exceptions
|
||||||
|
{
|
||||||
|
class IllegalMemoryAccess: public TargetOperationFailure
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit IllegalMemoryAccess()
|
||||||
|
: TargetOperationFailure("Illegal memory access")
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit IllegalMemoryAccess(const std::string& message)
|
||||||
|
: TargetOperationFailure(message)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "src/Services/StringService.hpp"
|
#include "src/Services/StringService.hpp"
|
||||||
#include "src/Logger/Logger.hpp"
|
#include "src/Logger/Logger.hpp"
|
||||||
|
|
||||||
|
#include "Exceptions/IllegalMemoryAccess.hpp"
|
||||||
|
|
||||||
#include "src/Exceptions/Exception.hpp"
|
#include "src/Exceptions/Exception.hpp"
|
||||||
#include "src/Exceptions/InvalidConfig.hpp"
|
#include "src/Exceptions/InvalidConfig.hpp"
|
||||||
#include "src/TargetController/Exceptions/TargetOperationFailure.hpp"
|
#include "src/TargetController/Exceptions/TargetOperationFailure.hpp"
|
||||||
@@ -307,6 +309,20 @@ namespace Targets::RiscV
|
|||||||
return this->programmingMode;
|
return this->programmingMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RiscV::probeMemory(
|
||||||
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
|
Targets::TargetMemoryAddress address
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
this->riscVDebugInterface->readMemory(addressSpaceDescriptor, memorySegmentDescriptor, address, 4, {});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const Exceptions::IllegalMemoryAccess&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RiscV::applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor) {
|
void RiscV::applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor) {
|
||||||
for (auto& [segmentKey, segmentDescriptor] : addressSpaceDescriptor.segmentDescriptorsByKey) {
|
for (auto& [segmentKey, segmentDescriptor] : addressSpaceDescriptor.segmentDescriptorsByKey) {
|
||||||
this->riscVDebugInterface->applyAccessRestrictions(segmentDescriptor);
|
this->riscVDebugInterface->applyAccessRestrictions(segmentDescriptor);
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ namespace Targets::RiscV
|
|||||||
|
|
||||||
bool programmingMode = false;
|
bool programmingMode = false;
|
||||||
|
|
||||||
|
bool probeMemory(
|
||||||
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
|
TargetMemoryAddress address
|
||||||
|
);
|
||||||
|
|
||||||
void applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor);
|
void applyDebugInterfaceAccessRestrictions(TargetAddressSpaceDescriptor& addressSpaceDescriptor);
|
||||||
void applyDebugInterfaceAccessRestrictions(TargetRegisterGroupDescriptor& registerGroupDescriptor);
|
void applyDebugInterfaceAccessRestrictions(TargetRegisterGroupDescriptor& registerGroupDescriptor);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "src/Logger/Logger.hpp"
|
|
||||||
#include "src/Exceptions/InvalidConfig.hpp"
|
#include "src/Exceptions/InvalidConfig.hpp"
|
||||||
|
#include "src/Exceptions/Exception.hpp"
|
||||||
|
|
||||||
|
#include "src/Logger/Logger.hpp"
|
||||||
|
|
||||||
namespace Targets::RiscV::Wch
|
namespace Targets::RiscV::Wch
|
||||||
{
|
{
|
||||||
@@ -13,10 +15,24 @@ namespace Targets::RiscV::Wch
|
|||||||
TargetDescriptionFile&& targetDescriptionFile
|
TargetDescriptionFile&& targetDescriptionFile
|
||||||
)
|
)
|
||||||
: RiscV(targetConfig, targetDescriptionFile)
|
: RiscV(targetConfig, targetDescriptionFile)
|
||||||
|
, targetConfig(WchRiscVTargetConfig{RiscV::targetConfig})
|
||||||
, targetDescriptionFile(std::move(targetDescriptionFile))
|
, targetDescriptionFile(std::move(targetDescriptionFile))
|
||||||
, programMemorySegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory"))
|
, mappedSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("mapped_program_memory"))
|
||||||
, mappedProgramMemorySegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("mapped_progmem"))
|
, mainProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program"))
|
||||||
{}
|
, bootProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("boot_program"))
|
||||||
|
, peripheralSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("peripherals"))
|
||||||
|
, selectedProgramSegmentDescriptor(
|
||||||
|
this->targetConfig.programSegmentKey.has_value()
|
||||||
|
&& *(this->targetConfig.programSegmentKey) == this->bootProgramSegmentDescriptor.key
|
||||||
|
? this->bootProgramSegmentDescriptor
|
||||||
|
: this->mainProgramSegmentDescriptor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Logger::info(
|
||||||
|
"Selected program memory segment: \"" + this->selectedProgramSegmentDescriptor.name + "\" (\""
|
||||||
|
+ this->selectedProgramSegmentDescriptor.key + "\")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void WchRiscV::activate() {
|
void WchRiscV::activate() {
|
||||||
RiscV::activate();
|
RiscV::activate();
|
||||||
@@ -89,7 +105,7 @@ namespace Targets::RiscV::Wch
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& sysAddressSpaceDescriptor = descriptor.getAddressSpaceDescriptor("system");
|
auto& sysAddressSpaceDescriptor = descriptor.getAddressSpaceDescriptor("system");
|
||||||
sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory").inspectionEnabled = true;
|
sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program").inspectionEnabled = true;
|
||||||
sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_ram").inspectionEnabled = true;
|
sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_ram").inspectionEnabled = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -103,7 +119,7 @@ namespace Targets::RiscV::Wch
|
|||||||
* See the overridden WchRiscV::writeMemory() member function below, for more.
|
* See the overridden WchRiscV::writeMemory() member function below, for more.
|
||||||
*/
|
*/
|
||||||
sysAddressSpaceDescriptor.getMemorySegmentDescriptor(
|
sysAddressSpaceDescriptor.getMemorySegmentDescriptor(
|
||||||
this->mappedProgramMemorySegmentDescriptor.key
|
this->mappedSegmentDescriptor.key
|
||||||
).programmingModeAccess.writeable = true;
|
).programmingModeAccess.writeable = true;
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
@@ -112,12 +128,12 @@ namespace Targets::RiscV::Wch
|
|||||||
void WchRiscV::setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
|
void WchRiscV::setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
|
||||||
if (
|
if (
|
||||||
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
|
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
|
||||||
&& breakpoint.memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor
|
&& breakpoint.memorySegmentDescriptor == this->mappedSegmentDescriptor
|
||||||
) {
|
) {
|
||||||
this->riscVDebugInterface->setProgramBreakpoint(TargetProgramBreakpoint{
|
this->riscVDebugInterface->setProgramBreakpoint(TargetProgramBreakpoint{
|
||||||
.addressSpaceDescriptor = this->sysAddressSpaceDescriptor,
|
.addressSpaceDescriptor = this->sysAddressSpaceDescriptor,
|
||||||
.memorySegmentDescriptor = this->getDestinationProgramMemorySegmentDescriptor(),
|
.memorySegmentDescriptor = this->selectedProgramSegmentDescriptor,
|
||||||
.address = this->transformAliasedProgramMemoryAddress(breakpoint.address),
|
.address = this->transformMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor),
|
||||||
.size = breakpoint.size,
|
.size = breakpoint.size,
|
||||||
.type = breakpoint.type
|
.type = breakpoint.type
|
||||||
});
|
});
|
||||||
@@ -131,12 +147,12 @@ namespace Targets::RiscV::Wch
|
|||||||
void WchRiscV::removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
|
void WchRiscV::removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
|
||||||
if (
|
if (
|
||||||
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
|
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
|
||||||
&& breakpoint.memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor
|
&& breakpoint.memorySegmentDescriptor == this->mappedSegmentDescriptor
|
||||||
) {
|
) {
|
||||||
this->riscVDebugInterface->removeProgramBreakpoint(TargetProgramBreakpoint{
|
this->riscVDebugInterface->removeProgramBreakpoint(TargetProgramBreakpoint{
|
||||||
.addressSpaceDescriptor = this->sysAddressSpaceDescriptor,
|
.addressSpaceDescriptor = this->sysAddressSpaceDescriptor,
|
||||||
.memorySegmentDescriptor = this->getDestinationProgramMemorySegmentDescriptor(),
|
.memorySegmentDescriptor = this->selectedProgramSegmentDescriptor,
|
||||||
.address = this->transformAliasedProgramMemoryAddress(breakpoint.address),
|
.address = this->transformMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor),
|
||||||
.size = breakpoint.size,
|
.size = breakpoint.size,
|
||||||
.type = breakpoint.type
|
.type = breakpoint.type
|
||||||
});
|
});
|
||||||
@@ -147,52 +163,183 @@ namespace Targets::RiscV::Wch
|
|||||||
this->riscVDebugInterface->removeProgramBreakpoint(breakpoint);
|
this->riscVDebugInterface->removeProgramBreakpoint(breakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TargetMemoryBuffer WchRiscV::readMemory(
|
||||||
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
|
TargetMemoryAddress startAddress,
|
||||||
|
TargetMemorySize bytes,
|
||||||
|
const std::set<TargetMemoryAddressRange>& excludedAddressRanges
|
||||||
|
) {
|
||||||
|
using Services::StringService;
|
||||||
|
|
||||||
|
if (memorySegmentDescriptor == this->mappedSegmentDescriptor) {
|
||||||
|
const auto& aliasedSegment = this->selectedProgramSegmentDescriptor;
|
||||||
|
const auto transformedAddress = this->transformMappedAddress(startAddress, aliasedSegment);
|
||||||
|
|
||||||
|
const auto addressRange = TargetMemoryAddressRange{
|
||||||
|
transformedAddress,
|
||||||
|
static_cast<TargetMemoryAddress>(transformedAddress + bytes - 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!aliasedSegment.addressRange.contains(addressRange)) {
|
||||||
|
throw Exceptions::Exception{
|
||||||
|
"Read access range (0x" + StringService::toHex(addressRange.startAddress) + " -> 0x"
|
||||||
|
+ StringService::toHex(addressRange.endAddress) + ", " + std::to_string(addressRange.size())
|
||||||
|
+ " bytes) exceeds the boundary of the selected program segment \"" + aliasedSegment.key
|
||||||
|
+ "\" (0x" + StringService::toHex(aliasedSegment.addressRange.startAddress) + " -> 0x"
|
||||||
|
+ StringService::toHex(aliasedSegment.addressRange.endAddress) + ", "
|
||||||
|
+ std::to_string(aliasedSegment.addressRange.size()) + " bytes)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return RiscV::readMemory(
|
||||||
|
addressSpaceDescriptor,
|
||||||
|
aliasedSegment,
|
||||||
|
transformedAddress,
|
||||||
|
bytes,
|
||||||
|
excludedAddressRanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RiscV::readMemory(
|
||||||
|
addressSpaceDescriptor,
|
||||||
|
memorySegmentDescriptor,
|
||||||
|
startAddress,
|
||||||
|
bytes,
|
||||||
|
excludedAddressRanges
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void WchRiscV::writeMemory(
|
void WchRiscV::writeMemory(
|
||||||
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
TargetMemoryAddress startAddress,
|
TargetMemoryAddress startAddress,
|
||||||
TargetMemoryBufferSpan buffer
|
TargetMemoryBufferSpan buffer
|
||||||
) {
|
) {
|
||||||
/*
|
using Services::StringService;
|
||||||
* WCH targets have an alias segment that maps to either the program memory segment or the boot program
|
|
||||||
* memory segment.
|
|
||||||
*
|
|
||||||
* Reading directly from this memory segment is fine, but we cannot write to it - the operation just fails
|
|
||||||
* silently. We handle this by forwarding any write operations on that segment to the appropriate (aliased)
|
|
||||||
* segment.
|
|
||||||
*
|
|
||||||
* @TODO: Currently, this just assumes that the alias segment always maps to the program memory segment, but I
|
|
||||||
* believe it may map to the boot program memory segment in some cases. This needs to be revisited
|
|
||||||
* before v2.0.0.
|
|
||||||
*/
|
|
||||||
if (memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor) {
|
|
||||||
const auto transformedAddress = this->transformAliasedProgramMemoryAddress(startAddress);
|
|
||||||
assert(this->programMemorySegmentDescriptor.addressRange.contains(transformedAddress));
|
|
||||||
|
|
||||||
return RiscV::writeMemory(
|
if (memorySegmentDescriptor == this->mappedSegmentDescriptor) {
|
||||||
addressSpaceDescriptor,
|
const auto& aliasedSegment = this->selectedProgramSegmentDescriptor;
|
||||||
this->programMemorySegmentDescriptor,
|
const auto transformedAddress = this->transformMappedAddress(startAddress, aliasedSegment);
|
||||||
|
|
||||||
|
const auto addressRange = TargetMemoryAddressRange{
|
||||||
transformedAddress,
|
transformedAddress,
|
||||||
buffer
|
static_cast<TargetMemoryAddress>(transformedAddress + buffer.size() - 1)
|
||||||
);
|
};
|
||||||
|
|
||||||
|
if (!aliasedSegment.addressRange.contains(addressRange)) {
|
||||||
|
throw Exceptions::Exception{
|
||||||
|
"Write access range (0x" + StringService::toHex(addressRange.startAddress) + " -> 0x"
|
||||||
|
+ StringService::toHex(addressRange.endAddress) + ", " + std::to_string(addressRange.size())
|
||||||
|
+ " bytes) exceeds the boundary of the selected program segment \"" + aliasedSegment.key
|
||||||
|
+ "\" (0x" + StringService::toHex(aliasedSegment.addressRange.startAddress) + " -> 0x"
|
||||||
|
+ StringService::toHex(aliasedSegment.addressRange.endAddress) + ", "
|
||||||
|
+ std::to_string(aliasedSegment.addressRange.size()) + " bytes)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return RiscV::writeMemory(addressSpaceDescriptor, aliasedSegment, transformedAddress, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RiscV::writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer);
|
return RiscV::writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TargetMemorySegmentDescriptor& WchRiscV::getDestinationProgramMemorySegmentDescriptor() {
|
void WchRiscV::eraseMemory(
|
||||||
return this->programMemorySegmentDescriptor;
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
|
||||||
|
) {
|
||||||
|
if (memorySegmentDescriptor == this->mappedSegmentDescriptor) {
|
||||||
|
return RiscV::eraseMemory(addressSpaceDescriptor, this->selectedProgramSegmentDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
RiscV::eraseMemory(addressSpaceDescriptor, memorySegmentDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetMemoryAddress WchRiscV::transformAliasedProgramMemoryAddress(TargetMemoryAddress address) const {
|
TargetMemoryAddress WchRiscV::getProgramCounter() {
|
||||||
|
const auto programCounter = RiscV::getProgramCounter();
|
||||||
|
|
||||||
|
if (this->mappedSegmentDescriptor.addressRange.contains(programCounter)) {
|
||||||
|
const auto& actualAliasedSegment = this->resolveAliasedMemorySegment();
|
||||||
|
if (actualAliasedSegment != this->selectedProgramSegmentDescriptor) {
|
||||||
|
/*
|
||||||
|
* The target's mapped segment no longer aliases the selected program segment.
|
||||||
|
*
|
||||||
|
* Imagine starting a debug session with GDB, then replacing the entire program being debugged with a
|
||||||
|
* totally different program, whilst GDB is still running and the same debug session is still active.
|
||||||
|
* Understandably, GDB would become very confused by this, as it has no idea what just happened, or why
|
||||||
|
* the program it was observing just moments ago has suddenly disappeared and been replaced by another.
|
||||||
|
*
|
||||||
|
* This is essentially what has just happened. The mapped segment initially aliased one segment in
|
||||||
|
* program memory, but now, all of a sudden, it appears to be aliasing a different segment. This can
|
||||||
|
* happen when the target switches to a different mode of operation. When the target is in "user mode",
|
||||||
|
* the mapped segment aliases the main program segment. But when the target is in "boot mode", the
|
||||||
|
* mapped segment aliases the boot segment. The program running on the target can invoke a mode switch
|
||||||
|
* by writing to a register and performing a software reset.
|
||||||
|
*
|
||||||
|
* So, we have a program counter that's addressing a totally different program, but to most external
|
||||||
|
* entities, it will appear as if it's addressing the same program.
|
||||||
|
*
|
||||||
|
* In order to avoid causing havoc and potentially misleading the user, we transform the PC to its
|
||||||
|
* aliased address. That way, it will be clear to all external entities, that the target is currently
|
||||||
|
* executing code in a different memory segment to the one that was selected for debugging.
|
||||||
|
*/
|
||||||
|
Logger::warning(
|
||||||
|
"The mapped program memory segment is currently aliasing a foreign segment (\""
|
||||||
|
+ actualAliasedSegment.key + "\") - the program counter will be transformed to its aliased"
|
||||||
|
" address"
|
||||||
|
);
|
||||||
|
return this->transformMappedAddress(programCounter, actualAliasedSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return programCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TargetMemorySegmentDescriptor& WchRiscV::resolveAliasedMemorySegment() {
|
||||||
|
/*
|
||||||
|
* To determine the aliased segment, we probe the boundary of the boot segment via the mapped segment.
|
||||||
|
*
|
||||||
|
* Assumptions that must hold, for this to work:
|
||||||
|
* - The boot segment must be smaller than the main program memory segment
|
||||||
|
* - Breaching the boundary of the boot segment must always result in an exception (out-of-bounds error)
|
||||||
|
*
|
||||||
|
* If the mapped segment is aliasing the boot segment, the memory access will fail, due to an out-of-bounds
|
||||||
|
* error. If the access succeeds, we can be fairly certain the mapped segment is aliasing the main program
|
||||||
|
* memory segment.
|
||||||
|
*
|
||||||
|
* I did consider using the FLASH_STATR peripheral register to determine the aliased segment, but not all WCH
|
||||||
|
* targets have the required bit fields for that to work. And even the ones that do, do not behave in the way
|
||||||
|
* described by the documentation.
|
||||||
|
*/
|
||||||
|
const auto probeAddress = this->bootProgramSegmentDescriptor.addressRange.endAddress
|
||||||
|
- this->bootProgramSegmentDescriptor.addressRange.startAddress
|
||||||
|
+ this->mappedSegmentDescriptor.addressRange.startAddress + 1;
|
||||||
|
|
||||||
|
assert(this->sysAddressSpaceDescriptor.addressRange.contains(probeAddress));
|
||||||
|
assert(this->mainProgramSegmentDescriptor.size() > this->bootProgramSegmentDescriptor.size());
|
||||||
|
|
||||||
|
const auto& segment = this->probeMemory(
|
||||||
|
this->sysAddressSpaceDescriptor,
|
||||||
|
this->mappedSegmentDescriptor,
|
||||||
|
probeAddress
|
||||||
|
) ? this->mainProgramSegmentDescriptor : this->bootProgramSegmentDescriptor;
|
||||||
|
|
||||||
|
Logger::debug("Aliased program memory segment: \"" + segment.key + "\"");
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetMemoryAddress WchRiscV::transformMappedAddress(
|
||||||
|
TargetMemoryAddress address,
|
||||||
|
const TargetMemorySegmentDescriptor& segmentDescriptor
|
||||||
|
) {
|
||||||
using Services::StringService;
|
using Services::StringService;
|
||||||
|
|
||||||
const auto transformedAddress = address - this->mappedProgramMemorySegmentDescriptor.addressRange.startAddress
|
const auto transformedAddress = address - this->mappedSegmentDescriptor.addressRange.startAddress
|
||||||
+ this->programMemorySegmentDescriptor.addressRange.startAddress;
|
+ segmentDescriptor.addressRange.startAddress;
|
||||||
|
|
||||||
Logger::debug(
|
Logger::debug(
|
||||||
"Transformed mapped program memory address 0x" + StringService::toHex(address) + " to 0x"
|
"Transformed mapped program memory address 0x" + StringService::toHex(address) + " to 0x"
|
||||||
+ StringService::toHex(transformedAddress)
|
+ StringService::toHex(transformedAddress) + " (segment: \"" + segmentDescriptor.key + "\")"
|
||||||
);
|
);
|
||||||
|
|
||||||
return transformedAddress;
|
return transformedAddress;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "src/Targets/RiscV/RiscV.hpp"
|
#include "src/Targets/RiscV/RiscV.hpp"
|
||||||
|
|
||||||
|
#include "WchRiscVTargetConfig.hpp"
|
||||||
#include "TargetDescriptionFile.hpp"
|
#include "TargetDescriptionFile.hpp"
|
||||||
|
|
||||||
namespace Targets::RiscV::Wch
|
namespace Targets::RiscV::Wch
|
||||||
@@ -22,22 +23,60 @@ namespace Targets::RiscV::Wch
|
|||||||
void setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override;
|
void setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override;
|
||||||
void removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override;
|
void removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) override;
|
||||||
|
|
||||||
|
TargetMemoryBuffer readMemory(
|
||||||
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
|
TargetMemoryAddress startAddress,
|
||||||
|
TargetMemorySize bytes,
|
||||||
|
const std::set<TargetMemoryAddressRange>& excludedAddressRanges
|
||||||
|
) override;
|
||||||
void writeMemory(
|
void writeMemory(
|
||||||
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
|
||||||
TargetMemoryAddress startAddress,
|
TargetMemoryAddress startAddress,
|
||||||
TargetMemoryBufferSpan buffer
|
TargetMemoryBufferSpan buffer
|
||||||
) override;
|
) override;
|
||||||
|
void eraseMemory(
|
||||||
|
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
|
||||||
|
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
|
||||||
|
) override;
|
||||||
|
|
||||||
|
TargetMemoryAddress getProgramCounter() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
WchRiscVTargetConfig targetConfig;
|
||||||
TargetDescriptionFile targetDescriptionFile;
|
TargetDescriptionFile targetDescriptionFile;
|
||||||
std::optional<std::reference_wrapper<const TargetDescription::Variant>> variant = std::nullopt;
|
std::optional<std::reference_wrapper<const TargetDescription::Variant>> variant = std::nullopt;
|
||||||
|
|
||||||
const TargetMemorySegmentDescriptor& programMemorySegmentDescriptor;
|
const TargetMemorySegmentDescriptor& mappedSegmentDescriptor;
|
||||||
const TargetMemorySegmentDescriptor& bootProgramMemorySegmentDescriptor;
|
const TargetMemorySegmentDescriptor& mainProgramSegmentDescriptor;
|
||||||
const TargetMemorySegmentDescriptor& mappedProgramMemorySegmentDescriptor;
|
const TargetMemorySegmentDescriptor& bootProgramSegmentDescriptor;
|
||||||
|
const TargetMemorySegmentDescriptor& peripheralSegmentDescriptor;
|
||||||
|
|
||||||
const TargetMemorySegmentDescriptor& getDestinationProgramMemorySegmentDescriptor();
|
/*
|
||||||
TargetMemoryAddress transformAliasedProgramMemoryAddress(TargetMemoryAddress address) const;
|
* The selected program segment is the program memory segment the user has selected to be the subject of all
|
||||||
|
* memory accesses that are forwarded from the mapped program memory segment.
|
||||||
|
*
|
||||||
|
* In other words, whenever we service a memory access via the mapped program memory segment, we perform the
|
||||||
|
* operation on the selected program segment, instead. This prevents memory access requests from being
|
||||||
|
* misinterpreted in cases where the WCH target has switched to/from boot mode.
|
||||||
|
*
|
||||||
|
* The user can use the "program_segment_key" config param to specify the selected segment. If not provided,
|
||||||
|
* the selected segment defaults to the main program memory segment. This means, by default, we assume the user
|
||||||
|
* intends to debug the main user program, residing on the main program segment, as opposed to the boot program
|
||||||
|
* residing on the boot segment. If the user intends to debug their boot program, they must select the boot
|
||||||
|
* segment as the program segment, via the "program_segment_key" config param.
|
||||||
|
*
|
||||||
|
* I will explain this more clearly in the user documentation, on the Bloom website.
|
||||||
|
*
|
||||||
|
* For more, see the implementation of the memory access member functions.
|
||||||
|
*/
|
||||||
|
const TargetMemorySegmentDescriptor& selectedProgramSegmentDescriptor;
|
||||||
|
|
||||||
|
const TargetMemorySegmentDescriptor& resolveAliasedMemorySegment();
|
||||||
|
TargetMemoryAddress transformMappedAddress(
|
||||||
|
TargetMemoryAddress address,
|
||||||
|
const TargetMemorySegmentDescriptor& segmentDescriptor
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/Targets/RiscV/Wch/WchRiscVTargetConfig.cpp
Normal file
14
src/Targets/RiscV/Wch/WchRiscVTargetConfig.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "WchRiscVTargetConfig.hpp"
|
||||||
|
|
||||||
|
namespace Targets::RiscV
|
||||||
|
{
|
||||||
|
WchRiscVTargetConfig::WchRiscVTargetConfig(const RiscVTargetConfig& targetConfig)
|
||||||
|
: RiscVTargetConfig(targetConfig)
|
||||||
|
{
|
||||||
|
const auto& targetNode = targetConfig.targetNode;
|
||||||
|
|
||||||
|
if (targetNode["program_segment_key"]) {
|
||||||
|
this->programSegmentKey = targetNode["program_segment_key"].as<std::string>("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Targets/RiscV/Wch/WchRiscVTargetConfig.hpp
Normal file
16
src/Targets/RiscV/Wch/WchRiscVTargetConfig.hpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "src/Targets/RiscV/RiscVTargetConfig.hpp"
|
||||||
|
|
||||||
|
namespace Targets::RiscV
|
||||||
|
{
|
||||||
|
struct WchRiscVTargetConfig: public RiscVTargetConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WchRiscVTargetConfig(const RiscVTargetConfig& targetConfig);
|
||||||
|
|
||||||
|
std::optional<std::string> programSegmentKey;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user