Delta programming - where we only upload what's changed

This commit is contained in:
Nav
2025-02-01 23:13:45 +00:00
parent 70ec49c7ac
commit d52c46ec2a
33 changed files with 918 additions and 289 deletions

View File

@@ -55,8 +55,8 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
debugSession.programmingSession.reset(); debugSession.programmingSession.reset();
Logger::warning("Program memory updated");
targetControllerService.disableProgrammingMode(); targetControllerService.disableProgrammingMode();
Logger::warning("Program memory updated");
Logger::warning("Resetting target"); Logger::warning("Resetting target");
targetControllerService.resetTarget(); targetControllerService.resetTarget();

View File

@@ -54,8 +54,6 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
try { try {
targetControllerService.enableProgrammingMode(); targetControllerService.enableProgrammingMode();
Logger::warning("Erasing program memory, in preparation for programming");
// We don't erase a specific address range - we just erase the entire program memory. // We don't erase a specific address range - we just erase the entire program memory.
targetControllerService.eraseMemory( targetControllerService.eraseMemory(
gdbTargetDescriptor.programAddressSpaceDescriptor, gdbTargetDescriptor.programAddressSpaceDescriptor,

View File

@@ -55,8 +55,10 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
if (!debugSession.programmingSession.has_value()) { if (!debugSession.programmingSession.has_value()) {
debugSession.programmingSession = ProgrammingSession{this->startAddress, this->buffer}; debugSession.programmingSession = ProgrammingSession{this->startAddress, this->buffer};
debugSession.connection.writePacket(OkResponsePacket{});
return;
}
} else {
auto& programmingSession = debugSession.programmingSession.value(); auto& programmingSession = debugSession.programmingSession.value();
const auto currentEndAddress = programmingSession.startAddress + programmingSession.buffer.size() - 1; const auto currentEndAddress = programmingSession.startAddress + programmingSession.buffer.size() - 1;
const auto expectedStartAddress = (currentEndAddress + 1); const auto expectedStartAddress = (currentEndAddress + 1);
@@ -79,7 +81,6 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets
this->buffer.begin(), this->buffer.begin(),
this->buffer.end() this->buffer.end()
); );
}
debugSession.connection.writePacket(OkResponsePacket{}); debugSession.connection.writePacket(OkResponsePacket{});

View File

@@ -80,8 +80,8 @@ namespace DebugServer::Gdb::RiscVGdb::CommandPackets
debugSession.programmingSession.reset(); debugSession.programmingSession.reset();
Logger::warning("Program memory updated");
targetControllerService.disableProgrammingMode(); targetControllerService.disableProgrammingMode();
Logger::warning("Program memory updated");
Logger::warning("Resetting target"); Logger::warning("Resetting target");
targetControllerService.resetTarget(); targetControllerService.resetTarget();

View File

@@ -59,7 +59,7 @@ namespace DebugServer::Gdb::RiscVGdb::CommandPackets
throw Exception{"Memory segment (\"" + segmentDescriptor.name + "\") not writable in programming mode"}; throw Exception{"Memory segment (\"" + segmentDescriptor.name + "\") not writable in programming mode"};
} }
Logger::warning("Erasing \"" + segmentDescriptor.name + "\" segment, in preparation for programming"); Logger::debug("Erase segment key: `" + segmentDescriptor.key + "`");
targetControllerService.enableProgrammingMode(); targetControllerService.enableProgrammingMode();

View File

@@ -55,8 +55,10 @@ namespace DebugServer::Gdb::RiscVGdb::CommandPackets
if (!debugSession.programmingSession.has_value()) { if (!debugSession.programmingSession.has_value()) {
debugSession.programmingSession = ProgrammingSession{this->startAddress, this->buffer}; debugSession.programmingSession = ProgrammingSession{this->startAddress, this->buffer};
debugSession.connection.writePacket(OkResponsePacket{});
return;
}
} else {
auto& programmingSession = debugSession.programmingSession.value(); auto& programmingSession = debugSession.programmingSession.value();
const auto currentEndAddress = programmingSession.startAddress + programmingSession.buffer.size() - 1; const auto currentEndAddress = programmingSession.startAddress + programmingSession.buffer.size() - 1;
const auto expectedStartAddress = (currentEndAddress + 1); const auto expectedStartAddress = (currentEndAddress + 1);
@@ -79,7 +81,6 @@ namespace DebugServer::Gdb::RiscVGdb::CommandPackets
this->buffer.begin(), this->buffer.begin(),
this->buffer.end() this->buffer.end()
); );
}
debugSession.connection.writePacket(OkResponsePacket{}); debugSession.connection.writePacket(OkResponsePacket{});

View File

@@ -397,6 +397,15 @@ namespace DebugToolDrivers::Microchip::Protocols::Edbg::Avr
} }
} }
void EdbgAvr8Interface::clearAllBreakpoints() {
this->clearAllSoftwareBreakpoints();
// Clear all hardware breakpoints
while (!this->hardwareBreakpointNumbersByAddress.empty()) {
this->clearHardwareBreakpoint(this->hardwareBreakpointNumbersByAddress.begin()->first);
}
}
TargetRegisterDescriptorAndValuePairs EdbgAvr8Interface::readRegisters( TargetRegisterDescriptorAndValuePairs EdbgAvr8Interface::readRegisters(
const Targets::TargetRegisterDescriptors& descriptors const Targets::TargetRegisterDescriptors& descriptors
) { ) {
@@ -868,27 +877,6 @@ namespace DebugToolDrivers::Microchip::Protocols::Edbg::Avr
return; return;
} }
/*
* When clearing individual software breakpoints via EDBG tools, the breakpoints are not actually removed
* until this next program flow command.
*
* This wouldn't be a problem, if the tool handled the stale breakpoint removals properly, when programming
* the target (overwriting the program memory at which the software breakpoints reside). But the tool
* doesn't handle this properly - it seems to completely ignore the fact that the program memory has been
* updated, and so it will corrupt the new program by overwriting the program memory where the old
* software breakpoints used to reside. The memory is overwritten with the old instruction - the one that
* was captured at the time the software breakpoint was inserted. So we end up with a corrupted program.
*
* To avoid this issue, we send the 'clear all software breakpoints' command to the tool, just before
* entering programming mode. That command will clear all breakpoints immediately, preventing program
* memory corruption at the next flow control command.
*
* The TargetController will reinsert all breakpoints at the end of a programming session, so the breakpoints
* that we clear here will be restored.
*/
Logger::debug("Clearing all software breakpoints in preparation for programming mode");
this->clearAllSoftwareBreakpoints();
const auto responseFrame = this->edbgInterface->sendAvrCommandFrameAndWaitForResponseFrame( const auto responseFrame = this->edbgInterface->sendAvrCommandFrameAndWaitForResponseFrame(
EnterProgrammingMode{} EnterProgrammingMode{}
); );
@@ -1312,15 +1300,6 @@ namespace DebugToolDrivers::Microchip::Protocols::Edbg::Avr
this->pendingSoftwareBreakpointDeletions.clear(); this->pendingSoftwareBreakpointDeletions.clear();
} }
void EdbgAvr8Interface::clearAllBreakpoints() {
this->clearAllSoftwareBreakpoints();
// Clear all hardware breakpoints
while (!this->hardwareBreakpointNumbersByAddress.empty()) {
this->clearHardwareBreakpoint(this->hardwareBreakpointNumbersByAddress.begin()->first);
}
}
void EdbgAvr8Interface::injectActiveBreakpoints( void EdbgAvr8Interface::injectActiveBreakpoints(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor,

View File

@@ -113,8 +113,9 @@ namespace DebugToolDrivers::Microchip::Protocols::Edbg::Avr
Targets::Microchip::Avr8::TargetSignature getDeviceId() override; Targets::Microchip::Avr8::TargetSignature getDeviceId() override;
virtual void setProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) override; void setProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) override;
virtual void removeProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) override; void removeProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) override;
void clearAllBreakpoints() override;
Targets::TargetRegisterDescriptorAndValuePairs readRegisters( Targets::TargetRegisterDescriptorAndValuePairs readRegisters(
const Targets::TargetRegisterDescriptors& descriptors const Targets::TargetRegisterDescriptors& descriptors
@@ -247,7 +248,6 @@ namespace DebugToolDrivers::Microchip::Protocols::Edbg::Avr
void setHardwareBreakpoint(Targets::TargetMemoryAddress address); void setHardwareBreakpoint(Targets::TargetMemoryAddress address);
void clearHardwareBreakpoint(Targets::TargetMemoryAddress address); void clearHardwareBreakpoint(Targets::TargetMemoryAddress address);
void clearAllSoftwareBreakpoints(); void clearAllSoftwareBreakpoints();
void clearAllBreakpoints();
void injectActiveBreakpoints( void injectActiveBreakpoints(
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor,

View File

@@ -99,6 +99,7 @@ namespace DebugToolDrivers::TargetInterfaces::Microchip::Avr8
virtual void setProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) = 0; virtual void setProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) = 0;
virtual void removeProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) = 0; virtual void removeProgramBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint) = 0;
virtual void clearAllBreakpoints() = 0;
virtual Targets::TargetMemoryAddress getProgramCounter() = 0; virtual Targets::TargetMemoryAddress getProgramCounter() = 0;
virtual void setProgramCounter(Targets::TargetMemoryAddress programCounter) = 0; virtual void setProgramCounter(Targets::TargetMemoryAddress programCounter) = 0;

View File

@@ -21,8 +21,6 @@
#include "src/Helpers/BiMap.hpp" #include "src/Helpers/BiMap.hpp"
#include "src/Services/StringService.hpp" #include "src/Services/StringService.hpp"
#include "src/Logger/Logger.hpp"
#include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp" #include "src/TargetController/Exceptions/DeviceCommunicationFailure.hpp"
namespace DebugToolDrivers::Wch::Protocols::WchLink namespace DebugToolDrivers::Wch::Protocols::WchLink

View File

@@ -33,7 +33,9 @@ namespace DebugToolDrivers::Wch
using ::Targets::TargetAddressSpaceDescriptor; using ::Targets::TargetAddressSpaceDescriptor;
using ::Targets::TargetMemorySegmentDescriptor; using ::Targets::TargetMemorySegmentDescriptor;
using ::Targets::TargetProgramBreakpoint; using ::Targets::TargetProgramBreakpoint;
using ::Targets::BreakpointResources;
using ::Targets::TargetMemorySegmentType; using ::Targets::TargetMemorySegmentType;
using ::Targets::TargetRegisterDescriptor;
using ::Targets::TargetRegisterDescriptors; using ::Targets::TargetRegisterDescriptors;
using ::Targets::TargetRegisterDescriptorAndValuePairs; using ::Targets::TargetRegisterDescriptorAndValuePairs;
@@ -62,6 +64,7 @@ namespace DebugToolDrivers::Wch
, toolInfo(toolInfo) , toolInfo(toolInfo)
, sysAddressSpaceDescriptor(this->targetDescriptionFile.getSystemAddressSpaceDescriptor()) , sysAddressSpaceDescriptor(this->targetDescriptionFile.getSystemAddressSpaceDescriptor())
, mainProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program")) , mainProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("main_program"))
, bootProgramSegmentDescriptor(this->sysAddressSpaceDescriptor.getMemorySegmentDescriptor("boot_program"))
, flashProgramOpcodes( , flashProgramOpcodes(
WchLinkDebugInterface::getFlashProgramOpcodes( WchLinkDebugInterface::getFlashProgramOpcodes(
this->targetDescriptionFile.getProperty("wch_link_interface", "programming_opcode_key").value this->targetDescriptionFile.getProperty("wch_link_interface", "programming_opcode_key").value
@@ -129,7 +132,6 @@ namespace DebugToolDrivers::Wch
} }
void WchLinkDebugInterface::deactivate() { void WchLinkDebugInterface::deactivate() {
this->riscVTranslator.clearAllTriggers();
this->riscVTranslator.deactivate(); this->riscVTranslator.deactivate();
const auto response = this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::DetachTarget{}); const auto response = this->wchLinkInterface.sendCommandAndWaitForResponse(Commands::Control::DetachTarget{});
@@ -142,7 +144,7 @@ namespace DebugToolDrivers::Wch
return "0x" + Services::StringService::toHex(this->cachedVariantId.value()); return "0x" + Services::StringService::toHex(this->cachedVariantId.value());
} }
Targets::TargetExecutionState WchLinkDebugInterface::getExecutionState() { TargetExecutionState WchLinkDebugInterface::getExecutionState() {
return this->riscVTranslator.getExecutionState(); return this->riscVTranslator.getExecutionState();
} }
@@ -162,7 +164,7 @@ namespace DebugToolDrivers::Wch
this->riscVTranslator.reset(); this->riscVTranslator.reset();
} }
Targets::BreakpointResources WchLinkDebugInterface::getBreakpointResources() { BreakpointResources WchLinkDebugInterface::getBreakpointResources() {
return { return {
.hardwareBreakpoints = this->riscVTranslator.getTriggerCount(), .hardwareBreakpoints = this->riscVTranslator.getTriggerCount(),
.softwareBreakpoints = 0xFFFFFFFF, // TODO: Use the program memory size to determine the limit. .softwareBreakpoints = 0xFFFFFFFF, // TODO: Use the program memory size to determine the limit.
@@ -187,22 +189,22 @@ namespace DebugToolDrivers::Wch
} }
} }
Targets::TargetRegisterDescriptorAndValuePairs WchLinkDebugInterface::readCpuRegisters( TargetRegisterDescriptorAndValuePairs WchLinkDebugInterface::readCpuRegisters(
const Targets::TargetRegisterDescriptors& descriptors const TargetRegisterDescriptors& descriptors
) { ) {
return this->riscVTranslator.readCpuRegisters(descriptors); return this->riscVTranslator.readCpuRegisters(descriptors);
} }
void WchLinkDebugInterface::writeCpuRegisters(const Targets::TargetRegisterDescriptorAndValuePairs& registers) { void WchLinkDebugInterface::writeCpuRegisters(const TargetRegisterDescriptorAndValuePairs& registers) {
return this->riscVTranslator.writeCpuRegisters(registers); return this->riscVTranslator.writeCpuRegisters(registers);
} }
Targets::TargetMemoryBuffer WchLinkDebugInterface::readMemory( TargetMemoryBuffer WchLinkDebugInterface::readMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
Targets::TargetMemoryAddress startAddress, TargetMemoryAddress startAddress,
Targets::TargetMemorySize bytes, TargetMemorySize bytes,
const std::set<Targets::TargetMemoryAddressRange>& excludedAddressRanges const std::set<TargetMemoryAddressRange>& excludedAddressRanges
) { ) {
return this->riscVTranslator.readMemory( return this->riscVTranslator.readMemory(
addressSpaceDescriptor, addressSpaceDescriptor,
@@ -216,8 +218,8 @@ namespace DebugToolDrivers::Wch
void WchLinkDebugInterface::writeMemory( void WchLinkDebugInterface::writeMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor, const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor, const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
Targets::TargetMemoryAddress startAddress, TargetMemoryAddress startAddress,
Targets::TargetMemoryBufferSpan buffer TargetMemoryBufferSpan buffer
) { ) {
if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) { if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) {
/* /*
@@ -293,8 +295,8 @@ namespace DebugToolDrivers::Wch
* The erase operation will silently fail, and the target will be left in a bad state, causing the subsequent * The erase operation will silently fail, and the target will be left in a bad state, causing the subsequent
* full block write to corrupt the target's program memory * full block write to corrupt the target's program memory
* *
* - The reset is what causes the erase operation to fail. But I have no idea why. I've inspected the relevant * - The reset is what causes the erase operation to fail, but I have no idea why. I've inspected the relevant
* registers, at the relevant times, but have found nothing significant that could explain this * registers, at the relevant times, but found nothing significant that could explain this
* *
* - Subsequent resets do not fix the issue, but another full block write does. My guess is that the first full * - Subsequent resets do not fix the issue, but another full block write does. My guess is that the first full
* block write (which corrupted program memory) corrected the target state, cleaning the mess made by the * block write (which corrupted program memory) corrected the target state, cleaning the mess made by the
@@ -304,11 +306,11 @@ namespace DebugToolDrivers::Wch
} }
void WchLinkDebugInterface::enableProgrammingMode() { void WchLinkDebugInterface::enableProgrammingMode() {
// TODO: Move this to target driver. After v2.0.0.
this->clearAllBreakpoints();
} }
void WchLinkDebugInterface::disableProgrammingMode() { void WchLinkDebugInterface::disableProgrammingMode() {}
this->softwareBreakpointRegistry.clear();
}
void WchLinkDebugInterface::applyAccessRestrictions(TargetMemorySegmentDescriptor& memorySegmentDescriptor) { void WchLinkDebugInterface::applyAccessRestrictions(TargetMemorySegmentDescriptor& memorySegmentDescriptor) {
if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) { if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) {
@@ -320,7 +322,7 @@ namespace DebugToolDrivers::Wch
} }
} }
void WchLinkDebugInterface::applyAccessRestrictions(Targets::TargetRegisterDescriptor& registerDescriptor) { void WchLinkDebugInterface::applyAccessRestrictions(TargetRegisterDescriptor& registerDescriptor) {
// I don't believe any further access restrictions are required for registers. TODO: Review after v2.0.0. // I don't believe any further access restrictions are required for registers. TODO: Review after v2.0.0.
} }
@@ -329,23 +331,6 @@ namespace DebugToolDrivers::Wch
throw Exception{"Invalid software breakpoint size (" + std::to_string(breakpoint.size) + ")"}; throw Exception{"Invalid software breakpoint size (" + std::to_string(breakpoint.size) + ")"};
} }
const auto originalData = this->readMemory(
breakpoint.addressSpaceDescriptor,
breakpoint.memorySegmentDescriptor,
breakpoint.address,
breakpoint.size,
{}
);
const auto softwareBreakpoint = ::Targets::RiscV::ProgramBreakpoint{
breakpoint,
static_cast<::Targets::RiscV::Opcodes::Opcode>(
breakpoint.size == 2
? (originalData[1] << 8) | originalData[0]
: (originalData[3] << 24) | (originalData[2] << 16) | (originalData[1] << 8) | originalData[0]
)
};
static constexpr auto EBREAK_OPCODE = std::to_array<unsigned char>({ static constexpr auto EBREAK_OPCODE = std::to_array<unsigned char>({
static_cast<unsigned char>(::Targets::RiscV::Opcodes::Ebreak), static_cast<unsigned char>(::Targets::RiscV::Opcodes::Ebreak),
static_cast<unsigned char>(::Targets::RiscV::Opcodes::Ebreak >> 8), static_cast<unsigned char>(::Targets::RiscV::Opcodes::Ebreak >> 8),
@@ -359,15 +344,15 @@ namespace DebugToolDrivers::Wch
}); });
this->writeMemory( this->writeMemory(
softwareBreakpoint.addressSpaceDescriptor, breakpoint.addressSpaceDescriptor,
softwareBreakpoint.memorySegmentDescriptor, breakpoint.memorySegmentDescriptor,
softwareBreakpoint.address, breakpoint.address,
softwareBreakpoint.size == 2 breakpoint.size == 2
? TargetMemoryBufferSpan{COMPRESSED_EBREAK_OPCODE} ? TargetMemoryBufferSpan{COMPRESSED_EBREAK_OPCODE}
: TargetMemoryBufferSpan{EBREAK_OPCODE} : TargetMemoryBufferSpan{EBREAK_OPCODE}
); );
this->softwareBreakpointRegistry.insert(softwareBreakpoint); this->softwareBreakpointRegistry.insert(breakpoint);
} }
void WchLinkDebugInterface::clearSoftwareBreakpoint(const TargetProgramBreakpoint& breakpoint) { void WchLinkDebugInterface::clearSoftwareBreakpoint(const TargetProgramBreakpoint& breakpoint) {
@@ -375,37 +360,26 @@ namespace DebugToolDrivers::Wch
throw Exception{"Invalid software breakpoint size (" + std::to_string(breakpoint.size) + ")"}; throw Exception{"Invalid software breakpoint size (" + std::to_string(breakpoint.size) + ")"};
} }
const auto softwareBreakpointOpt = this->softwareBreakpointRegistry.find(breakpoint);
if (!softwareBreakpointOpt.has_value()) {
throw TargetOperationFailure{
"Unknown software breakpoint (byte address: 0x" + Services::StringService::toHex(breakpoint.address)
+ ")"
};
}
const auto& softwareBreakpoint = softwareBreakpointOpt->get();
if (!softwareBreakpoint.originalInstruction.has_value()) {
throw InternalFatalErrorException{"Missing original opcode"};
}
this->writeMemory( this->writeMemory(
softwareBreakpoint.addressSpaceDescriptor, breakpoint.addressSpaceDescriptor,
softwareBreakpoint.memorySegmentDescriptor, breakpoint.memorySegmentDescriptor,
softwareBreakpoint.address, breakpoint.address,
softwareBreakpoint.size == 2 TargetMemoryBufferSpan{
? TargetMemoryBuffer{ breakpoint.originalData.begin(),
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction)), breakpoint.originalData.begin() + breakpoint.size
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction) >> 8)
}
: TargetMemoryBuffer{
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction)),
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction) >> 8),
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction) >> 16),
static_cast<unsigned char>(*(softwareBreakpoint.originalInstruction) >> 24)
} }
); );
this->softwareBreakpointRegistry.remove(softwareBreakpoint); this->softwareBreakpointRegistry.remove(breakpoint);
}
void WchLinkDebugInterface::clearAllBreakpoints() {
this->riscVTranslator.clearAllTriggers();
for (const auto [addressSpaceId, breakpointsByAddress] : this->softwareBreakpointRegistry) {
for (const auto& [address, breakpoint] : breakpointsByAddress) {
this->clearSoftwareBreakpoint(breakpoint);
}
}
} }
void WchLinkDebugInterface::writeProgramMemoryPartialBlock( void WchLinkDebugInterface::writeProgramMemoryPartialBlock(

View File

@@ -17,7 +17,6 @@
#include "src/Targets/ProgramBreakpointRegistry.hpp" #include "src/Targets/ProgramBreakpointRegistry.hpp"
#include "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp" #include "src/Targets/RiscV/Wch/TargetDescriptionFile.hpp"
#include "src/Targets/RiscV/ProgramBreakpoint.hpp"
namespace DebugToolDrivers::Wch namespace DebugToolDrivers::Wch
{ {
@@ -96,6 +95,7 @@ namespace DebugToolDrivers::Wch
const Targets::TargetAddressSpaceDescriptor sysAddressSpaceDescriptor; const Targets::TargetAddressSpaceDescriptor sysAddressSpaceDescriptor;
const Targets::TargetMemorySegmentDescriptor& mainProgramSegmentDescriptor; const Targets::TargetMemorySegmentDescriptor& mainProgramSegmentDescriptor;
const Targets::TargetMemorySegmentDescriptor& bootProgramSegmentDescriptor;
/** /**
* The 'target activation' command returns a payload of 5 bytes. * The 'target activation' command returns a payload of 5 bytes.
@@ -107,13 +107,14 @@ namespace DebugToolDrivers::Wch
std::optional<WchTargetVariantId> cachedVariantId; std::optional<WchTargetVariantId> cachedVariantId;
std::optional<WchTargetId> cachedTargetId; std::optional<WchTargetId> cachedTargetId;
Targets::ProgramBreakpointRegistryGeneric<Targets::RiscV::ProgramBreakpoint> softwareBreakpointRegistry; Targets::ProgramBreakpointRegistry softwareBreakpointRegistry;
std::span<const unsigned char> flashProgramOpcodes; std::span<const unsigned char> flashProgramOpcodes;
Targets::TargetMemorySize programmingBlockSize; Targets::TargetMemorySize programmingBlockSize;
void setSoftwareBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint); void setSoftwareBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint);
void clearSoftwareBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint); void clearSoftwareBreakpoint(const Targets::TargetProgramBreakpoint& breakpoint);
void clearAllBreakpoints();
void writeProgramMemoryPartialBlock( void writeProgramMemoryPartialBlock(
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,

View File

@@ -213,6 +213,10 @@ TargetConfig::TargetConfig(const YAML::Node& targetNode) {
this->programMemoryCache = targetNode["program_memory_cache"].as<bool>(this->programMemoryCache); this->programMemoryCache = targetNode["program_memory_cache"].as<bool>(this->programMemoryCache);
} }
if (targetNode["delta_programming"]) {
this->deltaProgramming = targetNode["delta_programming"].as<bool>(this->deltaProgramming);
}
if (targetNode["reserve_stepping_breakpoint"]) { if (targetNode["reserve_stepping_breakpoint"]) {
this->reserveSteppingBreakpoint = targetNode["reserve_stepping_breakpoint"].as<bool>(false); this->reserveSteppingBreakpoint = targetNode["reserve_stepping_breakpoint"].as<bool>(false);
} }

View File

@@ -56,6 +56,13 @@ struct TargetConfig
*/ */
bool programMemoryCache = true; bool programMemoryCache = true;
/**
* Determines whether Bloom will employ "delta programming" during programming sessions.
*
* Not all targets support delta programming.
*/
bool deltaProgramming = true;
/** /**
* Determines if Bloom will reserve a single hardware breakpoint for stepping operations. * Determines if Bloom will reserve a single hardware breakpoint for stepping operations.
*/ */

View File

@@ -220,7 +220,7 @@ namespace Services
addressSpaceDescriptor, addressSpaceDescriptor,
memorySegmentDescriptor, memorySegmentDescriptor,
startAddress, startAddress,
buffer TargetMemoryBuffer{buffer.begin(), buffer.end()}
), ),
this->defaultTimeout, this->defaultTimeout,
this->activeAtomicSessionId this->activeAtomicSessionId

View File

@@ -288,7 +288,7 @@ namespace Services
std::optional<TargetController::AtomicSessionIdType> activeAtomicSessionId = std::nullopt; std::optional<TargetController::AtomicSessionIdType> activeAtomicSessionId = std::nullopt;
std::chrono::milliseconds defaultTimeout = std::chrono::milliseconds{30000}; std::chrono::milliseconds defaultTimeout = std::chrono::milliseconds{90000};
TargetController::AtomicSessionIdType startAtomicSession(); TargetController::AtomicSessionIdType startAtomicSession();
void endAtomicSession(TargetController::AtomicSessionIdType sessionId); void endAtomicSession(TargetController::AtomicSessionIdType sessionId);

View File

@@ -18,18 +18,18 @@ namespace TargetController::Commands
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor; const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor;
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor; const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor;
Targets::TargetMemoryAddress startAddress; Targets::TargetMemoryAddress startAddress;
Targets::TargetMemoryBufferSpan buffer; Targets::TargetMemoryBuffer buffer;
WriteTargetMemory( WriteTargetMemory(
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor, const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor, const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor,
Targets::TargetMemoryAddress startAddress, Targets::TargetMemoryAddress startAddress,
Targets::TargetMemoryBufferSpan buffer Targets::TargetMemoryBuffer&& buffer
) )
: addressSpaceDescriptor(addressSpaceDescriptor) : addressSpaceDescriptor(addressSpaceDescriptor)
, memorySegmentDescriptor(memorySegmentDescriptor) , memorySegmentDescriptor(memorySegmentDescriptor)
, startAddress(startAddress) , startAddress(startAddress)
, buffer(buffer) , buffer(std::move(buffer))
{}; {};
[[nodiscard]] CommandType getType() const override { [[nodiscard]] CommandType getType() const override {

View File

@@ -13,6 +13,7 @@
#include "src/Services/PathService.hpp" #include "src/Services/PathService.hpp"
#include "src/Services/ProcessService.hpp" #include "src/Services/ProcessService.hpp"
#include "src/Services/StringService.hpp" #include "src/Services/StringService.hpp"
#include "src/Services/AlignmentService.hpp"
#include "src/Logger/Logger.hpp" #include "src/Logger/Logger.hpp"
#include "Exceptions/TargetOperationFailure.hpp" #include "Exceptions/TargetOperationFailure.hpp"
@@ -287,9 +288,34 @@ namespace TargetController
this->acquireHardware(); this->acquireHardware();
if (this->targetState->executionState != TargetExecutionState::RUNNING) { this->targetState = std::make_unique<TargetState>(
// this->target->run(); TargetExecutionState::UNKNOWN,
// this->targetState->executionState = TargetExecutionState::RUNNING; TargetMode::DEBUGGING,
std::nullopt
);
this->refreshExecutionState();
if (this->environmentConfig.targetConfig.hardwareBreakpoints) {
const auto& breakpointResources = this->targetDescriptor->breakpointResources;
Logger::info("Available hardware breakpoints: " + std::to_string(breakpointResources.hardwareBreakpoints));
Logger::info(
"Reserved hardware breakpoints: " + std::to_string(breakpointResources.reservedHardwareBreakpoints)
);
} else {
Logger::warning("Hardware breakpoints have been disabled");
}
if (
this->targetState->executionState == TargetExecutionState::STOPPED
&& this->environmentConfig.targetConfig.resumeOnStartup
) {
try {
this->resumeTarget();
} catch (const Exceptions::TargetOperationFailure& exception) {
Logger::error("Failed to resume target execution on startup - error: " + exception.getMessage());
}
} }
this->state = TargetControllerState::ACTIVE; this->state = TargetControllerState::ACTIVE;
@@ -569,35 +595,7 @@ namespace TargetController
this->target->postActivate(); this->target->postActivate();
this->targetState = std::make_unique<TargetState>( this->deltaProgrammingInterface = this->target->deltaProgrammingInterface();
TargetExecutionState::UNKNOWN,
TargetMode::DEBUGGING,
std::nullopt
);
this->refreshExecutionState();
if (this->environmentConfig.targetConfig.hardwareBreakpoints) {
const auto& breakpointResources = this->targetDescriptor->breakpointResources;
Logger::info("Available hardware breakpoints: " + std::to_string(breakpointResources.hardwareBreakpoints));
Logger::info(
"Reserved hardware breakpoints: " + std::to_string(breakpointResources.reservedHardwareBreakpoints)
);
} else {
Logger::warning("Hardware breakpoints have been disabled");
}
if (
this->targetState->executionState == TargetExecutionState::STOPPED
&& this->environmentConfig.targetConfig.resumeOnStartup
) {
try {
this->resumeTarget();
} catch (const Exceptions::TargetOperationFailure& exception) {
Logger::error("Failed to resume target execution on startup - error: " + exception.getMessage());
}
}
} }
void TargetControllerComponent::releaseHardware() { void TargetControllerComponent::releaseHardware() {
@@ -752,7 +750,8 @@ namespace TargetController
); );
} }
return cache.fetch(startAddress, bytes); const auto cachedData = cache.fetch(startAddress, bytes);
return TargetMemoryBuffer{cachedData.begin(), cachedData.end()};
} }
return this->target->readMemory( return this->target->readMemory(
@@ -783,7 +782,13 @@ namespace TargetController
this->target->writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer); this->target->writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer);
if (isProgramMemory && this->environmentConfig.targetConfig.programMemoryCache) { if (
isProgramMemory
&& (
this->environmentConfig.targetConfig.programMemoryCache
|| this->environmentConfig.targetConfig.deltaProgramming
)
) {
this->getProgramMemoryCache(memorySegmentDescriptor).insert(startAddress, buffer); this->getProgramMemoryCache(memorySegmentDescriptor).insert(startAddress, buffer);
} }
@@ -811,7 +816,10 @@ namespace TargetController
throw Exception{"Cannot erase program memory - programming mode not enabled."}; throw Exception{"Cannot erase program memory - programming mode not enabled."};
} }
if (this->environmentConfig.targetConfig.programMemoryCache) { if (
this->environmentConfig.targetConfig.programMemoryCache
|| this->environmentConfig.targetConfig.deltaProgramming
) {
Logger::debug("Clearing program memory cache"); Logger::debug("Clearing program memory cache");
this->getProgramMemoryCache(memorySegmentDescriptor).clear(); this->getProgramMemoryCache(memorySegmentDescriptor).clear();
} }
@@ -847,7 +855,10 @@ namespace TargetController
if ( if (
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
&& this->environmentConfig.targetConfig.programMemoryCache && (
this->environmentConfig.targetConfig.programMemoryCache
|| this->environmentConfig.targetConfig.deltaProgramming
)
&& this->target->isProgramMemory( && this->target->isProgramMemory(
breakpoint.addressSpaceDescriptor, breakpoint.addressSpaceDescriptor,
breakpoint.memorySegmentDescriptor, breakpoint.memorySegmentDescriptor,
@@ -881,39 +892,43 @@ namespace TargetController
Logger::debug("Removing breakpoint at byte address 0x" + StringService::toHex(breakpoint.address)); Logger::debug("Removing breakpoint at byte address 0x" + StringService::toHex(breakpoint.address));
if (!registry.contains(breakpoint)) {
const auto registeredBreakpointOpt = registry.find(breakpoint);
if (!registeredBreakpointOpt.has_value()) {
Logger::debug("Breakpoint not found in registry - ignoring removal request"); Logger::debug("Breakpoint not found in registry - ignoring removal request");
return; return;
} }
this->target->removeProgramBreakpoint(breakpoint); const auto& registeredBreakpoint = registeredBreakpointOpt->get();
registry.remove(breakpoint); this->target->removeProgramBreakpoint(registeredBreakpoint);
if ( if (
breakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE registeredBreakpoint.type == TargetProgramBreakpoint::Type::SOFTWARE
&& this->environmentConfig.targetConfig.programMemoryCache && (
this->environmentConfig.targetConfig.programMemoryCache
|| this->environmentConfig.targetConfig.deltaProgramming
)
&& this->target->isProgramMemory( && this->target->isProgramMemory(
breakpoint.addressSpaceDescriptor, registeredBreakpoint.addressSpaceDescriptor,
breakpoint.memorySegmentDescriptor, registeredBreakpoint.memorySegmentDescriptor,
breakpoint.address, registeredBreakpoint.address,
breakpoint.size registeredBreakpoint.size
) )
) { ) {
auto& cache = this->getProgramMemoryCache(breakpoint.memorySegmentDescriptor); auto& cache = this->getProgramMemoryCache(registeredBreakpoint.memorySegmentDescriptor);
if (cache.contains(breakpoint.address, breakpoint.size)) { if (cache.contains(registeredBreakpoint.address, registeredBreakpoint.size)) {
// Update program memory cache // Update program memory cache with the original instruction
cache.insert( cache.insert(
breakpoint.address, registeredBreakpoint.address,
this->target->readMemory( TargetMemoryBufferSpan{
breakpoint.addressSpaceDescriptor, registeredBreakpoint.originalData.begin(),
breakpoint.memorySegmentDescriptor, registeredBreakpoint.originalData.begin() + registeredBreakpoint.size
breakpoint.address, }
breakpoint.size,
{}
)
); );
} }
} }
registry.remove(registeredBreakpoint);
} }
void TargetControllerComponent::clearAllBreakpoints() { void TargetControllerComponent::clearAllBreakpoints() {
@@ -935,12 +950,21 @@ namespace TargetController
this->target->enableProgrammingMode(); this->target->enableProgrammingMode();
Logger::warning("Programming mode enabled"); Logger::warning("Programming mode enabled");
if (this->environmentConfig.targetConfig.deltaProgramming && this->deltaProgrammingInterface != nullptr) {
this->deltaProgrammingSession = DeltaProgramming::Session{};
}
auto newState = *(this->targetState); auto newState = *(this->targetState);
newState.mode = TargetMode::PROGRAMMING; newState.mode = TargetMode::PROGRAMMING;
this->updateTargetState(newState); this->updateTargetState(newState);
} }
void TargetControllerComponent::disableProgrammingMode() { void TargetControllerComponent::disableProgrammingMode() {
if (this->deltaProgrammingSession.has_value()) {
this->commitDeltaProgrammingSession(*(this->deltaProgrammingSession));
this->deltaProgrammingSession = std::nullopt;
}
Logger::debug("Disabling programming mode"); Logger::debug("Disabling programming mode");
this->target->disableProgrammingMode(); this->target->disableProgrammingMode();
Logger::info("Programming mode disabled"); Logger::info("Programming mode disabled");
@@ -948,14 +972,41 @@ namespace TargetController
Logger::info("Restoring breakpoints"); Logger::info("Restoring breakpoints");
this->target->stop(); this->target->stop();
for (const auto& [addressSpaceId, breakpointsByAddress] : this->softwareBreakpointRegistry) { static const auto refreshOriginalData = [this] (TargetProgramBreakpoint& breakpoint) {
for (const auto& [address, breakpoint] : breakpointsByAddress) { const auto originalData = this->readTargetMemory(
breakpoint.addressSpaceDescriptor,
breakpoint.memorySegmentDescriptor,
breakpoint.address,
breakpoint.size,
{},
true
);
std::copy(originalData.begin(), originalData.end(), breakpoint.originalData.begin());
};
for (auto& [addressSpaceId, breakpointsByAddress] : this->softwareBreakpointRegistry) {
for (auto& [address, breakpoint] : breakpointsByAddress) {
refreshOriginalData(breakpoint);
this->target->setProgramBreakpoint(breakpoint); this->target->setProgramBreakpoint(breakpoint);
auto& cache = this->getProgramMemoryCache(breakpoint.memorySegmentDescriptor);
cache.insert(
breakpoint.address,
this->target->readMemory(
breakpoint.addressSpaceDescriptor,
breakpoint.memorySegmentDescriptor,
breakpoint.address,
breakpoint.size,
{}
)
);
} }
} }
for (const auto& [addressSpaceId, breakpointsByAddress] : this->hardwareBreakpointRegistry) { for (auto& [addressSpaceId, breakpointsByAddress] : this->hardwareBreakpointRegistry) {
for (const auto& [address, breakpoint] : breakpointsByAddress) { for (auto& [address, breakpoint] : breakpointsByAddress) {
refreshOriginalData(breakpoint);
this->target->setProgramBreakpoint(breakpoint); this->target->setProgramBreakpoint(breakpoint);
} }
} }
@@ -981,6 +1032,163 @@ namespace TargetController
return cacheIt->second; return cacheIt->second;
} }
void TargetControllerComponent::commitDeltaProgrammingSession(const DeltaProgramming::Session& session) {
using Services::AlignmentService;
using Services::StringService;
/*
* If a single write operation cannot be committed, we must abandon the whole session.
*
* We prepare CommitOperation objects and then commit all of them at the end, allowing for the abandoning of
* the session, if necessary.
*/
struct CommitOperation
{
const TargetAddressSpaceDescriptor& addressSpaceDescriptor;
const TargetMemorySegmentDescriptor& memorySegmentDescriptor;
std::vector<DeltaProgramming::Session::WriteOperation::Region> deltaSegments;
};
auto commitOperations = std::vector<CommitOperation>{};
for (const auto& [segmentId, writeOperation] : session.writeOperationsBySegmentId) {
auto& segmentCache = this->getProgramMemoryCache(writeOperation.memorySegmentDescriptor);
// Can the program memory cache facilitate diffing with all regions in this write operation?
for (const auto& region : writeOperation.regions) {
if (!segmentCache.contains(region.addressRange.startAddress, region.addressRange.size())) {
Logger::info("Abandoning delta programming session - insufficient data in program memory cache");
return this->abandonDeltaProgrammingSession(session);
}
}
const auto alignTo = this->deltaProgrammingInterface->deltaBlockSize(
writeOperation.addressSpaceDescriptor,
writeOperation.memorySegmentDescriptor
);
/*
* Ensure that the segment cache has sufficient data to facilitate delta segment alignment
*
* If the cache doesn't contain the necessary data for alignment, we just fill it with 0xFF instead of
* obtaining the data with a read operation. This saves us some time.
*/
for (const auto& region : writeOperation.regions) {
const auto alignedAddress = AlignmentService::alignMemoryAddress(
region.addressRange.startAddress,
alignTo
);
const auto alignedSize = AlignmentService::alignMemorySize(
region.addressRange.size() + (region.addressRange.startAddress - alignedAddress),
alignTo
);
if (!segmentCache.contains(alignedAddress, alignedSize)) {
if (region.addressRange.startAddress != alignedAddress) {
segmentCache.fill(alignedAddress, region.addressRange.startAddress - alignedAddress, 0xFF);
}
if (region.addressRange.size() != alignedSize) {
segmentCache.fill(
region.addressRange.endAddress + 1,
alignedSize - region.addressRange.size(),
0xFF
);
}
}
}
/*
* When constructing the delta segments, any software breakpoints currently installed in this memory
* segment can interfere with the diffing of the new program and the program memory cache.
*
* For this reason, we make a copy of the program memory cache and strip any software breakpoints from it,
* before constructing the delta segments.
*/
auto cacheData = segmentCache.data;
for (const auto& [addressSpaceId, breakpointsByAddress] : this->softwareBreakpointRegistry) {
for (const auto& [address, breakpoint] : breakpointsByAddress) {
if (breakpoint.memorySegmentDescriptor != writeOperation.memorySegmentDescriptor) {
continue;
}
std::copy(
breakpoint.originalData.begin(),
breakpoint.originalData.begin() + breakpoint.size,
cacheData.begin() + (breakpoint.address
- breakpoint.memorySegmentDescriptor.addressRange.startAddress)
);
}
}
auto operation = CommitOperation{
.addressSpaceDescriptor = writeOperation.addressSpaceDescriptor,
.memorySegmentDescriptor = writeOperation.memorySegmentDescriptor,
.deltaSegments = writeOperation.deltaSegments(cacheData, alignTo)
};
if (operation.deltaSegments.empty()) {
Logger::warning("Abandoning delta programming session - zero delta segments");
return this->abandonDeltaProgrammingSession(session);
}
if (
this->deltaProgrammingInterface->shouldAbandonSession(
operation.addressSpaceDescriptor,
operation.memorySegmentDescriptor,
operation.deltaSegments
)
) {
Logger::info("Abandoning delta programming session - upon target driver request");
return this->abandonDeltaProgrammingSession(session);
}
commitOperations.emplace_back(std::move(operation));
}
Logger::info("Committing delta programming session");
for (const auto& operation : commitOperations) {
auto& segmentCache = this->getProgramMemoryCache(operation.memorySegmentDescriptor);
Logger::info(
std::to_string(operation.deltaSegments.size()) + " delta segment(s) to be flushed to `"
+ operation.memorySegmentDescriptor.key + "`"
);
for (const auto& deltaSegment : operation.deltaSegments) {
Logger::info(
"Flushing delta segment 0x" + StringService::toHex(deltaSegment.addressRange.startAddress)
+ " -> 0x" + StringService::toHex(deltaSegment.addressRange.endAddress) + " - "
+ std::to_string(deltaSegment.buffer.size()) + " byte(s)"
);
this->target->writeMemory(
operation.addressSpaceDescriptor,
operation.memorySegmentDescriptor,
deltaSegment.addressRange.startAddress,
deltaSegment.buffer
);
segmentCache.insert(deltaSegment.addressRange.startAddress, deltaSegment.buffer);
}
}
}
void TargetControllerComponent::abandonDeltaProgrammingSession(const DeltaProgramming::Session& session) {
for (const auto& [segmentId, eraseOperation] : session.eraseOperationsBySegmentId) {
this->eraseTargetMemory(eraseOperation.addressSpaceDescriptor, eraseOperation.memorySegmentDescriptor);
}
for (const auto& [segmentId, writeOperation] : session.writeOperationsBySegmentId) {
for (const auto& region : writeOperation.regions) {
this->writeTargetMemory(
writeOperation.addressSpaceDescriptor,
writeOperation.memorySegmentDescriptor,
region.addressRange.startAddress,
region.buffer
);
}
}
}
void TargetControllerComponent::onShutdownTargetControllerEvent(const Events::ShutdownTargetController&) { void TargetControllerComponent::onShutdownTargetControllerEvent(const Events::ShutdownTargetController&) {
this->shutdown(); this->shutdown();
} }
@@ -1105,6 +1313,31 @@ namespace TargetController
throw Exception{"Invalid address range"}; throw Exception{"Invalid address range"};
} }
if (
this->targetState->mode == TargetMode::PROGRAMMING
&& this->deltaProgrammingSession.has_value()
&& this->target->isProgramMemory(
command.addressSpaceDescriptor,
command.memorySegmentDescriptor,
command.startAddress,
static_cast<TargetMemorySize>(command.buffer.size())
)
) {
Logger::debug(
"Pushing program memory write operation, at 0x" + Services::StringService::toHex(command.startAddress)
+ ", " + std::to_string(command.buffer.size()) + " byte(s), to active delta programming session"
);
this->deltaProgrammingSession->pushWriteOperation(
command.addressSpaceDescriptor,
command.memorySegmentDescriptor,
command.startAddress,
std::move(command.buffer)
);
return std::make_unique<Response>();
}
this->writeTargetMemory( this->writeTargetMemory(
command.addressSpaceDescriptor, command.addressSpaceDescriptor,
command.memorySegmentDescriptor, command.memorySegmentDescriptor,
@@ -1116,6 +1349,26 @@ namespace TargetController
} }
std::unique_ptr<Response> TargetControllerComponent::handleEraseTargetMemory(EraseTargetMemory& command) { std::unique_ptr<Response> TargetControllerComponent::handleEraseTargetMemory(EraseTargetMemory& command) {
if (
this->targetState->mode == TargetMode::PROGRAMMING
&& this->deltaProgrammingSession.has_value()
&& this->target->isProgramMemory(
command.addressSpaceDescriptor,
command.memorySegmentDescriptor,
command.memorySegmentDescriptor.addressRange.startAddress,
command.memorySegmentDescriptor.addressRange.size()
)
) {
Logger::debug("Pushing program memory erase operation to active delta programming session");
this->deltaProgrammingSession->pushEraseOperation(
command.addressSpaceDescriptor,
command.memorySegmentDescriptor
);
return std::make_unique<Response>();
}
this->eraseTargetMemory(command.addressSpaceDescriptor, command.memorySegmentDescriptor); this->eraseTargetMemory(command.addressSpaceDescriptor, command.memorySegmentDescriptor);
return std::make_unique<Response>(); return std::make_unique<Response>();
} }
@@ -1128,15 +1381,32 @@ namespace TargetController
std::unique_ptr<ProgramBreakpoint> TargetControllerComponent::handleSetProgramBreakpointBreakpointAnyType( std::unique_ptr<ProgramBreakpoint> TargetControllerComponent::handleSetProgramBreakpointBreakpointAnyType(
SetProgramBreakpointAnyType& command SetProgramBreakpointAnyType& command
) { ) {
const auto breakpoint = TargetProgramBreakpoint{ if (command.size > TargetProgramBreakpoint::MAX_SIZE) {
throw Exception{"Invalid breakpoint size"};
}
auto breakpoint = TargetProgramBreakpoint{
.addressSpaceDescriptor = command.addressSpaceDescriptor, .addressSpaceDescriptor = command.addressSpaceDescriptor,
.memorySegmentDescriptor = command.memorySegmentDescriptor, .memorySegmentDescriptor = command.memorySegmentDescriptor,
.address = command.address, .address = command.address,
.size = command.size, .size = command.size,
.type = this->environmentConfig.targetConfig.hardwareBreakpoints && this->availableHardwareBreakpoints() > 0 .type = this->environmentConfig.targetConfig.hardwareBreakpoints && this->availableHardwareBreakpoints() > 0
? TargetProgramBreakpoint::Type::HARDWARE ? TargetProgramBreakpoint::Type::HARDWARE
: TargetProgramBreakpoint::Type::SOFTWARE : TargetProgramBreakpoint::Type::SOFTWARE,
.originalData = {}
}; };
const auto originalData = this->readTargetMemory(
command.addressSpaceDescriptor,
command.memorySegmentDescriptor,
command.address,
command.size,
{},
true
);
std::copy(originalData.begin(), originalData.end(), breakpoint.originalData.begin());
this->setProgramBreakpoint(breakpoint); this->setProgramBreakpoint(breakpoint);
return std::make_unique<ProgramBreakpoint>(breakpoint); return std::make_unique<ProgramBreakpoint>(breakpoint);
} }

View File

@@ -73,6 +73,8 @@
#include "src/Targets/TargetMemorySegmentDescriptor.hpp" #include "src/Targets/TargetMemorySegmentDescriptor.hpp"
#include "src/Targets/ProgramBreakpointRegistry.hpp" #include "src/Targets/ProgramBreakpointRegistry.hpp"
#include "src/Targets/TargetMemoryCache.hpp" #include "src/Targets/TargetMemoryCache.hpp"
#include "src/Targets/DeltaProgramming/DeltaProgrammingInterface.hpp"
#include "src/Targets/DeltaProgramming/Session.hpp"
#include "src/EventManager/EventManager.hpp" #include "src/EventManager/EventManager.hpp"
#include "src/EventManager/EventListener.hpp" #include "src/EventManager/EventListener.hpp"
@@ -142,6 +144,8 @@ namespace TargetController
std::unique_ptr<DebugTool> debugTool = nullptr; std::unique_ptr<DebugTool> debugTool = nullptr;
std::unique_ptr<Targets::Target> target = nullptr; std::unique_ptr<Targets::Target> target = nullptr;
Targets::DeltaProgramming::DeltaProgrammingInterface* deltaProgrammingInterface = nullptr;
std::map< std::map<
Commands::CommandType, Commands::CommandType,
std::function<std::unique_ptr<Responses::Response>(Commands::Command&)> std::function<std::unique_ptr<Responses::Response>(Commands::Command&)>
@@ -170,6 +174,11 @@ namespace TargetController
*/ */
std::map<Targets::TargetMemorySegmentId, Targets::TargetMemoryCache> programMemoryCachesBySegmentId; std::map<Targets::TargetMemorySegmentId, Targets::TargetMemoryCache> programMemoryCachesBySegmentId;
/**
* Active delta programming session
*/
std::optional<Targets::DeltaProgramming::Session> deltaProgrammingSession;
/** /**
* Registers a handler function for a particular command type. * Registers a handler function for a particular command type.
* Only one handler function can be registered per command type. * Only one handler function can be registered per command type.
@@ -329,6 +338,9 @@ namespace TargetController
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor
); );
void commitDeltaProgrammingSession(const Targets::DeltaProgramming::Session& session);
void abandonDeltaProgrammingSession(const Targets::DeltaProgramming::Session& session);
/** /**
* Invokes a shutdown. * Invokes a shutdown.
* *

View File

@@ -17,6 +17,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/TargetMemoryCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TargetMemoryCache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TargetPhysicalInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TargetPhysicalInterface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DynamicRegisterValue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DynamicRegisterValue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DeltaProgramming/Session.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription/TargetDescriptionFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription/TargetDescriptionFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/Avr8/Avr8.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Microchip/Avr8/Avr8.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/Avr8/Avr8TargetConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Microchip/Avr8/Avr8TargetConfig.cpp

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Session.hpp"
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetAddressSpaceDescriptor.hpp"
#include "src/Targets/TargetMemorySegmentDescriptor.hpp"
namespace Targets::DeltaProgramming
{
class DeltaProgrammingInterface
{
public:
DeltaProgrammingInterface() = default;
virtual ~DeltaProgrammingInterface() = default;
/**
* The TargetController can align delta segments to a given block size, using the program memory cache.
* This can significantly reduce the number of read operations that would take place for page alignment,
* improving programming speed.
*
* This member function should return the necessary block size for the given memory segment. If alignment is
* not required, it should return a value of 1.
*/
virtual TargetMemorySize deltaBlockSize(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) = 0;
virtual bool shouldAbandonSession(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
const std::vector<Session::WriteOperation::Region>& deltaSegments
) = 0;
};
}

View File

@@ -0,0 +1,198 @@
#include "Session.hpp"
#include <algorithm>
#include <cassert>
#include "src/Services/AlignmentService.hpp"
namespace Targets::DeltaProgramming
{
using Targets::TargetAddressSpaceDescriptor;
using Targets::TargetMemorySegmentDescriptor;
using Targets::TargetMemoryAddress;
using Targets::TargetMemoryAddressRange;
using Targets::TargetMemoryBuffer;
void Session::WriteOperation::Region::mergeWith(const Region& other) {
assert(this->addressRange.intersectsWith(other.addressRange));
assert(this->addressRange.startAddress <= other.addressRange.startAddress);
const auto intersectingSize = other.addressRange.intersectingSize(this->addressRange);
if (intersectingSize < addressRange.size()) {
this->buffer.resize(this->buffer.size() + other.buffer.size() - intersectingSize);
this->addressRange.endAddress = static_cast<Targets::TargetMemoryAddress>(
this->addressRange.startAddress + this->buffer.size() - 1
);
}
std::copy(
other.buffer.begin(),
other.buffer.end(),
this->buffer.begin() + (other.addressRange.startAddress - this->addressRange.startAddress)
);
}
std::vector<Session::WriteOperation::Region> Session::WriteOperation::deltaSegments(
Targets::TargetMemoryBufferSpan cacheData,
Targets::TargetMemorySize blockSize
) const {
using Services::AlignmentService;
// First, we merge any overlapping regions and sort all regions by start address. This simplifies things
auto mergedRegions = std::vector<Region>{};
for (const auto& region : this->regions) {
for (auto& existingRegion : mergedRegions) {
if (!existingRegion.addressRange.intersectsWith(region.addressRange)) {
continue;
}
if (region.addressRange.startAddress >= existingRegion.addressRange.startAddress) {
existingRegion.mergeWith(region);
} else {
auto regionClone = region;
regionClone.mergeWith(existingRegion);
std::swap(existingRegion, regionClone);
}
goto CONTINUE_OUTER;
}
mergedRegions.emplace_back(region);
CONTINUE_OUTER:
continue;
}
std::sort(
mergedRegions.begin(),
mergedRegions.end(),
[] (const Region& regionA, const Region& regionB) {
return regionA.addressRange.startAddress < regionB.addressRange.startAddress;
}
);
auto output = std::vector<Region>{};
auto deltaSegment = std::optional<Region>{};
/*
* Given that delta segments must be aligned to a given block size, not all bytes in a delta segment will
* differ from the corresponding bytes in the cached data.
*
* We enforce alignment of all delta segments from the point of creation, and maintain alignment during any
* subsequent changes. This simplifies things.
*
* We use the cached data to align the segments. Initially, we populate the aligned segments with the cached
* data, and then gradually overwrite the bytes that differ.
*/
for (const auto& region : mergedRegions) {
for (auto i = std::size_t{0}; i < region.buffer.size(); ++i) {
const auto address = static_cast<TargetMemoryAddress>(region.addressRange.startAddress + i);
const auto cacheIndex = static_cast<std::size_t>(
address - this->memorySegmentDescriptor.addressRange.startAddress
);
const auto regionByte = region.buffer[i];
const auto cachedByte = cacheData[cacheIndex];
if (regionByte == cachedByte) {
continue;
}
if (deltaSegment.has_value() && address > (deltaSegment->addressRange.endAddress + blockSize)) {
/*
* This region byte is not within the boundary of the current delta segment, or in the neighbouring
* block, so commit and create a new one.
*/
output.emplace_back(std::move(*deltaSegment));
deltaSegment = std::nullopt;
}
if (!deltaSegment.has_value()) {
const auto alignedAddress = AlignmentService::alignMemoryAddress(address, blockSize);
const auto alignedSize = AlignmentService::alignMemorySize((address - alignedAddress) + 1, blockSize);
const auto cacheOffset = cacheData.begin() + static_cast<long>(
alignedAddress - this->memorySegmentDescriptor.addressRange.startAddress
);
deltaSegment = Region{
.addressRange = TargetMemoryAddressRange{alignedAddress, alignedAddress + alignedSize - 1},
.buffer = {cacheOffset, cacheOffset + alignedSize}
};
}
if (address > deltaSegment->addressRange.endAddress) {
/*
* This region byte is in the neighbouring block of the current delta segment.
*
* Instead of committing the segment and creating a new one, we just extend it by another block,
* to accommodate the byte. This reduces the number of segments (and therefore, the number of
* memory writes).
*/
const auto cacheOffset = cacheData.begin() + static_cast<long>(
(deltaSegment->addressRange.endAddress
- this->memorySegmentDescriptor.addressRange.startAddress) + 1
);
deltaSegment->buffer.insert(deltaSegment->buffer.end(), cacheOffset, cacheOffset + blockSize);
deltaSegment->addressRange.endAddress += blockSize;
}
deltaSegment->buffer[address - deltaSegment->addressRange.startAddress] = regionByte;
}
}
if (deltaSegment.has_value()) {
output.emplace_back(std::move(*deltaSegment));
}
return output;
}
void Session::pushEraseOperation(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) {
if (this->eraseOperationsBySegmentId.contains(memorySegmentDescriptor.id)) {
return;
}
this->eraseOperationsBySegmentId.emplace(
memorySegmentDescriptor.id,
EraseOperation{
.addressSpaceDescriptor = addressSpaceDescriptor,
.memorySegmentDescriptor = memorySegmentDescriptor,
}
);
}
void Session::pushWriteOperation(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
TargetMemoryAddress startAddress,
Targets::TargetMemoryBuffer&& buffer
) {
assert(!buffer.empty());
auto operationIt = this->writeOperationsBySegmentId.find(memorySegmentDescriptor.id);
if (operationIt == this->writeOperationsBySegmentId.end()) {
operationIt = this->writeOperationsBySegmentId.emplace(
memorySegmentDescriptor.id,
WriteOperation{
.addressSpaceDescriptor = addressSpaceDescriptor,
.memorySegmentDescriptor = memorySegmentDescriptor,
.regions = {},
}
).first;
}
operationIt->second.regions.emplace_back(
WriteOperation::Region{
.addressRange = TargetMemoryAddressRange{
startAddress,
static_cast<TargetMemoryAddress>(startAddress + buffer.size() - 1)
},
.buffer = std::move(buffer)
}
);
}
}

View File

@@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
#include <unordered_map>
#include <vector>
#include "src/Targets/TargetAddressSpaceDescriptor.hpp"
#include "src/Targets/TargetMemorySegmentDescriptor.hpp"
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetMemoryAddressRange.hpp"
namespace Targets::DeltaProgramming
{
struct Session
{
struct EraseOperation
{
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor;
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor;
};
struct WriteOperation
{
struct Region
{
Targets::TargetMemoryAddressRange addressRange;
Targets::TargetMemoryBuffer buffer;
void mergeWith(const Region& other);
};
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor;
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor;
std::vector<Region> regions;
[[nodiscard]] std::vector<Region> deltaSegments(
Targets::TargetMemoryBufferSpan cacheData,
Targets::TargetMemorySize blockSize
) const;
};
std::unordered_map<Targets::TargetMemorySegmentId, EraseOperation> eraseOperationsBySegmentId;
std::unordered_map<Targets::TargetMemorySegmentId, WriteOperation> writeOperationsBySegmentId;
void pushEraseOperation(
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor
);
void pushWriteOperation(
const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const Targets::TargetMemorySegmentDescriptor& memorySegmentDescriptor,
Targets::TargetMemoryAddress startAddress,
Targets::TargetMemoryBuffer&& buffer
);
};
}

View File

@@ -656,6 +656,7 @@ namespace Targets::Microchip::Avr8
return; return;
} }
this->avr8DebugInterface->clearAllBreakpoints();
this->avr8DebugInterface->enableProgrammingMode(); this->avr8DebugInterface->enableProgrammingMode();
this->activeProgrammingSession = ProgrammingSession(); this->activeProgrammingSession = ProgrammingSession();
} }
@@ -687,6 +688,32 @@ namespace Targets::Microchip::Avr8
return std::nullopt; return std::nullopt;
} }
DeltaProgramming::DeltaProgrammingInterface* Avr8::deltaProgrammingInterface() {
if (
this->targetConfig.physicalInterface == TargetPhysicalInterface::DEBUG_WIRE
|| this->targetConfig.physicalInterface == TargetPhysicalInterface::UPDI
) {
return this;
}
return nullptr;
}
TargetMemorySize Avr8::deltaBlockSize(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) {
return memorySegmentDescriptor.pageSize.value_or(1);
}
bool Avr8::shouldAbandonSession(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
const std::vector<DeltaProgramming::Session::WriteOperation::Region>& deltaSegments
) {
return false;
}
std::map<TargetPadId, GpioPadDescriptor> Avr8::generateGpioPadDescriptorMapping( std::map<TargetPadId, GpioPadDescriptor> Avr8::generateGpioPadDescriptorMapping(
const std::vector<TargetPeripheralDescriptor>& portPeripheralDescriptors const std::vector<TargetPeripheralDescriptor>& portPeripheralDescriptors
) { ) {

View File

@@ -22,6 +22,7 @@
#include "src/Targets/TargetBitFieldDescriptor.hpp" #include "src/Targets/TargetBitFieldDescriptor.hpp"
#include "src/Targets/TargetPadDescriptor.hpp" #include "src/Targets/TargetPadDescriptor.hpp"
#include "src/Targets/TargetBreakpoint.hpp" #include "src/Targets/TargetBreakpoint.hpp"
#include "src/Targets/DeltaProgramming/DeltaProgrammingInterface.hpp"
#include "TargetDescriptionFile.hpp" #include "TargetDescriptionFile.hpp"
@@ -29,7 +30,9 @@
namespace Targets::Microchip::Avr8 namespace Targets::Microchip::Avr8
{ {
class Avr8: public Target class Avr8
: public Target
, public DeltaProgramming::DeltaProgrammingInterface
{ {
public: public:
explicit Avr8(const TargetConfig& targetConfig, TargetDescriptionFile&& targetDescriptionFile); explicit Avr8(const TargetConfig& targetConfig, TargetDescriptionFile&& targetDescriptionFile);
@@ -111,6 +114,17 @@ namespace Targets::Microchip::Avr8
std::string passthroughCommandHelpText() override; std::string passthroughCommandHelpText() override;
std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) override; std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) override;
DeltaProgramming::DeltaProgrammingInterface* deltaProgrammingInterface() override;
TargetMemorySize deltaBlockSize(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) override;
bool shouldAbandonSession(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
const std::vector<DeltaProgramming::Session::WriteOperation::Region>& deltaSegments
) override;
protected: protected:
DebugToolDrivers::TargetInterfaces::TargetPowerManagementInterface* targetPowerManagementInterface = nullptr; DebugToolDrivers::TargetInterfaces::TargetPowerManagementInterface* targetPowerManagementInterface = nullptr;
DebugToolDrivers::TargetInterfaces::Microchip::Avr8::Avr8DebugInterface* avr8DebugInterface = nullptr; DebugToolDrivers::TargetInterfaces::Microchip::Avr8::Avr8DebugInterface* avr8DebugInterface = nullptr;

View File

@@ -95,6 +95,14 @@ namespace Targets
); );
} }
decltype(ProgramBreakpointRegistryGeneric::mapping)::iterator begin() noexcept {
return this->mapping.begin();
}
decltype(ProgramBreakpointRegistryGeneric::mapping)::iterator end() noexcept {
return this->mapping.end();
}
decltype(ProgramBreakpointRegistryGeneric::mapping)::const_iterator begin() const noexcept { decltype(ProgramBreakpointRegistryGeneric::mapping)::const_iterator begin() const noexcept {
return this->mapping.begin(); return this->mapping.begin();
} }

View File

@@ -1,23 +0,0 @@
#pragma once
#include <optional>
#include "src/Targets/TargetBreakpoint.hpp"
#include "Opcodes/Opcode.hpp"
namespace Targets::RiscV
{
struct ProgramBreakpoint: TargetProgramBreakpoint
{
std::optional<Opcodes::Opcode> originalInstruction = std::nullopt;
explicit ProgramBreakpoint(const TargetProgramBreakpoint& breakpoint)
: TargetProgramBreakpoint(breakpoint)
{}
explicit ProgramBreakpoint(const TargetProgramBreakpoint& breakpoint, Opcodes::Opcode originalInstruction)
: TargetProgramBreakpoint(breakpoint)
, originalInstruction(originalInstruction)
{}
};
}

View File

@@ -2,6 +2,7 @@
#include <utility> #include <utility>
#include <cassert> #include <cassert>
#include <algorithm>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
@@ -207,7 +208,8 @@ namespace Targets::RiscV::Wch
.memorySegmentDescriptor = this->selectedProgramSegmentDescriptor, .memorySegmentDescriptor = this->selectedProgramSegmentDescriptor,
.address = this->deAliasMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor), .address = this->deAliasMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor),
.size = breakpoint.size, .size = breakpoint.size,
.type = breakpoint.type .type = breakpoint.type,
.originalData = breakpoint.originalData
}); });
return; return;
@@ -233,7 +235,8 @@ namespace Targets::RiscV::Wch
.memorySegmentDescriptor = this->selectedProgramSegmentDescriptor, .memorySegmentDescriptor = this->selectedProgramSegmentDescriptor,
.address = this->deAliasMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor), .address = this->deAliasMappedAddress(breakpoint.address, this->selectedProgramSegmentDescriptor),
.size = breakpoint.size, .size = breakpoint.size,
.type = breakpoint.type .type = breakpoint.type,
.originalData = breakpoint.originalData
}); });
return; return;
@@ -613,6 +616,48 @@ namespace Targets::RiscV::Wch
return std::nullopt; return std::nullopt;
} }
DeltaProgramming::DeltaProgrammingInterface* WchRiscV::deltaProgrammingInterface() {
return this;
}
TargetMemorySize WchRiscV::deltaBlockSize(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) {
return 64;
}
bool WchRiscV::shouldAbandonSession(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
const std::vector<DeltaProgramming::Session::WriteOperation::Region>& deltaSegments
) {
/*
* Delta programming isn't always faster on WCH RISC-V targets with WCH-Link debug tools.
*
* This is because the debug tool can write to program memory with two methods - one better suited for small
* operations and the other for larger ones. If there are many small delta segments, we'd end up using the
* slower method many times, resulting in a negative impact on programming speed.
*
* For this reason, we abandon delta sessions if they consist of too many delta segments.
*
* See WchLinkDebugInterface::writeMemory() for more.
*
* TODO: Consider moving this to the WCH-Link driver, seeing as it's specific to that debug tool. In fact, I
* think the entire implementation of the DeltaProgrammingInterface should reside in the tool driver, as
* opposed to the target driver. Something to look at after v2.0.0.
*/
return
deltaSegments.size() > 5
|| std::count_if(
deltaSegments.begin(),
deltaSegments.end(),
[] (const DeltaProgramming::Session::WriteOperation::Region& segment) {
return segment.buffer.size() > 192;
}
) > 2;
}
const TargetMemorySegmentDescriptor& WchRiscV::resolveAliasedMemorySegment() { const TargetMemorySegmentDescriptor& WchRiscV::resolveAliasedMemorySegment() {
/* /*
* To determine the aliased segment, we probe the boundary of the boot segment via the mapped segment. * To determine the aliased segment, we probe the boundary of the boot segment via the mapped segment.

View File

@@ -9,6 +9,7 @@
#include "src/Targets/RiscV/RiscV.hpp" #include "src/Targets/RiscV/RiscV.hpp"
#include "src/Targets/TargetPeripheralDescriptor.hpp" #include "src/Targets/TargetPeripheralDescriptor.hpp"
#include "src/Targets/TargetPadDescriptor.hpp" #include "src/Targets/TargetPadDescriptor.hpp"
#include "src/Targets/DeltaProgramming/DeltaProgrammingInterface.hpp"
#include "WchRiscVTargetConfig.hpp" #include "WchRiscVTargetConfig.hpp"
#include "TargetDescriptionFile.hpp" #include "TargetDescriptionFile.hpp"
@@ -21,7 +22,7 @@ namespace Targets::RiscV::Wch
{ {
class WchRiscV class WchRiscV
: public ::Targets::RiscV::RiscV : public ::Targets::RiscV::RiscV
, public DeltaProgrammingInterface , public DeltaProgramming::DeltaProgrammingInterface
{ {
public: public:
WchRiscV(const TargetConfig& targetConfig, TargetDescriptionFile&& targetDescriptionFile); WchRiscV(const TargetConfig& targetConfig, TargetDescriptionFile&& targetDescriptionFile);
@@ -59,6 +60,17 @@ namespace Targets::RiscV::Wch
std::string passthroughCommandHelpText() override; std::string passthroughCommandHelpText() override;
std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) override; std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) override;
DeltaProgramming::DeltaProgrammingInterface* deltaProgrammingInterface() override;
TargetMemorySize deltaBlockSize(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) override;
bool shouldAbandonSession(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
const std::vector<DeltaProgramming::Session::WriteOperation::Region>& deltaSegments
) override;
protected: protected:
WchRiscVTargetConfig targetConfig; WchRiscVTargetConfig targetConfig;
TargetDescriptionFile targetDescriptionFile; TargetDescriptionFile targetDescriptionFile;

View File

@@ -24,6 +24,8 @@
#include "PassthroughCommand.hpp" #include "PassthroughCommand.hpp"
#include "PassthroughResponse.hpp" #include "PassthroughResponse.hpp"
#include "DeltaProgramming/DeltaProgrammingInterface.hpp"
#include "src/DebugToolDrivers/DebugTool.hpp" #include "src/DebugToolDrivers/DebugTool.hpp"
namespace Targets namespace Targets
@@ -152,11 +154,19 @@ namespace Targets
virtual TargetGpioPadDescriptorAndStatePairs getGpioPadStates(const TargetPadDescriptors& padDescriptors) = 0; virtual TargetGpioPadDescriptorAndStatePairs getGpioPadStates(const TargetPadDescriptors& padDescriptors) = 0;
virtual void setGpioPadState(const TargetPadDescriptor& padDescriptor, const TargetGpioPadState& state) = 0; virtual void setGpioPadState(const TargetPadDescriptor& padDescriptor, const TargetGpioPadState& state) = 0;
/**
* When enabling programming mode, the target driver is expected to clear all program breakpoints currently
* installed on the target.
*
* Before disabling program mode, the target controller will reinstall the necessary breakpoints.
*/
virtual void enableProgrammingMode() = 0; virtual void enableProgrammingMode() = 0;
virtual void disableProgrammingMode() = 0; virtual void disableProgrammingMode() = 0;
virtual bool programmingModeEnabled() = 0; virtual bool programmingModeEnabled() = 0;
virtual std::string passthroughCommandHelpText() = 0; virtual std::string passthroughCommandHelpText() = 0;
virtual std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) = 0; virtual std::optional<PassthroughResponse> invokePassthroughCommand(const PassthroughCommand& command) = 0;
virtual DeltaProgramming::DeltaProgrammingInterface* deltaProgrammingInterface() = 0;
}; };
} }

View File

@@ -1,8 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <optional> #include <array>
#include <cassert>
#include "TargetMemory.hpp" #include "TargetMemory.hpp"
#include "TargetAddressSpaceDescriptor.hpp" #include "TargetAddressSpaceDescriptor.hpp"
@@ -18,6 +17,8 @@ namespace Targets
struct TargetProgramBreakpoint struct TargetProgramBreakpoint
{ {
static constexpr auto MAX_SIZE = TargetMemorySize{4};
enum class Type: std::uint8_t enum class Type: std::uint8_t
{ {
HARDWARE, HARDWARE,
@@ -29,6 +30,7 @@ namespace Targets
TargetMemoryAddress address; TargetMemoryAddress address;
TargetMemorySize size; TargetMemorySize size;
Type type; Type type;
std::array<unsigned char, TargetProgramBreakpoint::MAX_SIZE> originalData;
}; };
struct BreakpointResources struct BreakpointResources

View File

@@ -1,6 +1,7 @@
#include "TargetMemoryCache.hpp" #include "TargetMemoryCache.hpp"
#include <algorithm> #include <algorithm>
#include <cassert>
#include "src/Exceptions/Exception.hpp" #include "src/Exceptions/Exception.hpp"
@@ -11,7 +12,7 @@ namespace Targets
, data(TargetMemoryBuffer(memorySegmentDescriptor.size(), 0x00)) , data(TargetMemoryBuffer(memorySegmentDescriptor.size(), 0x00))
{} {}
TargetMemoryBuffer TargetMemoryCache::fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const { TargetMemoryBufferSpan TargetMemoryCache::fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const {
const auto startIndex = startAddress - this->memorySegmentDescriptor.addressRange.startAddress; const auto startIndex = startAddress - this->memorySegmentDescriptor.addressRange.startAddress;
if ( if (
@@ -21,7 +22,7 @@ namespace Targets
throw Exceptions::Exception{"Invalid cache access"}; throw Exceptions::Exception{"Invalid cache access"};
} }
return TargetMemoryBuffer{this->data.begin() + startIndex, this->data.begin() + startIndex + bytes}; return TargetMemoryBufferSpan{this->data.begin() + startIndex, this->data.begin() + startIndex + bytes};
} }
bool TargetMemoryCache::contains(TargetMemoryAddress startAddress, TargetMemorySize bytes) const { bool TargetMemoryCache::contains(TargetMemoryAddress startAddress, TargetMemorySize bytes) const {
@@ -34,12 +35,30 @@ namespace Targets
} }
void TargetMemoryCache::insert(TargetMemoryAddress startAddress, TargetMemoryBufferSpan data) { void TargetMemoryCache::insert(TargetMemoryAddress startAddress, TargetMemoryBufferSpan data) {
const auto startIndex = startAddress - this->memorySegmentDescriptor.addressRange.startAddress; std::copy(
data.begin(),
data.end(),
this->data.begin() + (startAddress - this->memorySegmentDescriptor.addressRange.startAddress)
);
std::copy(data.begin(), data.end(), this->data.begin() + startIndex); this->trackSegment(startAddress, static_cast<TargetMemoryAddress>(startAddress + data.size() - 1));
}
const auto endAddress = static_cast<Targets::TargetMemoryAddress>(startAddress + data.size() - 1); void TargetMemoryCache::fill(TargetMemoryAddress startAddress, TargetMemorySize size, unsigned char value) {
assert(this->data.size() >= (startAddress - this->memorySegmentDescriptor.addressRange.startAddress) + size);
for (auto i = std::size_t{0}; i < size; ++i) {
this->data[i + (startAddress - this->memorySegmentDescriptor.addressRange.startAddress)] = value;
}
this->trackSegment(startAddress, static_cast<TargetMemoryAddress>(startAddress + size - 1));
}
void TargetMemoryCache::clear() {
this->populatedSegments.clear();
}
void TargetMemoryCache::trackSegment(TargetMemoryAddress startAddress, TargetMemoryAddress endAddress) {
const auto intersectingStartSegmentIt = this->intersectingSegment(startAddress); const auto intersectingStartSegmentIt = this->intersectingSegment(startAddress);
const auto intersectingEndSegmentIt = this->intersectingSegment(endAddress); const auto intersectingEndSegmentIt = this->intersectingSegment(endAddress);
@@ -95,10 +114,6 @@ namespace Targets
} }
} }
void TargetMemoryCache::clear() {
this->populatedSegments.clear();
}
TargetMemoryCache::SegmentIt TargetMemoryCache::intersectingSegment(TargetMemoryAddress address) const { TargetMemoryCache::SegmentIt TargetMemoryCache::intersectingSegment(TargetMemoryAddress address) const {
if (this->populatedSegments.empty()) { if (this->populatedSegments.empty()) {
return this->populatedSegments.end(); return this->populatedSegments.end();

View File

@@ -11,47 +11,20 @@ namespace Targets
class TargetMemoryCache class TargetMemoryCache
{ {
public: public:
explicit TargetMemoryCache(const TargetMemorySegmentDescriptor& memorySegmentDescriptor);
/**
* Fetches data from the cache.
*
* TODO: Change return type to TargetMemoryBufferSpan
*
* @param startAddress
* @param bytes
*
* @return
*/
[[nodiscard]] TargetMemoryBuffer fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const;
/**
* Checks if the cache currently holds data within the given address range.
*
* @param startAddress
* @param bytes
*
* @return
*/
[[nodiscard]] bool contains(TargetMemoryAddress startAddress, TargetMemorySize bytes) const;
/**
* Inserts data into the cache and performs any necessary bookkeeping.
*
* @param startAddress
* @param data
*/
void insert(TargetMemoryAddress startAddress, TargetMemoryBufferSpan data);
/**
* Clears the cache.
*/
void clear();
private:
const TargetMemorySegmentDescriptor& memorySegmentDescriptor; const TargetMemorySegmentDescriptor& memorySegmentDescriptor;
TargetMemoryBuffer data; TargetMemoryBuffer data;
explicit TargetMemoryCache(const TargetMemorySegmentDescriptor& memorySegmentDescriptor);
[[nodiscard]] TargetMemoryBufferSpan fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const;
[[nodiscard]] bool contains(TargetMemoryAddress startAddress, TargetMemorySize bytes) const;
void insert(TargetMemoryAddress startAddress, TargetMemoryBufferSpan data);
void fill(TargetMemoryAddress startAddress, TargetMemorySize size, unsigned char value);
void clear();
private:
/** /**
* A populated segment is just an address range in the cache that we know we've populated. * A populated segment is just an address range in the cache that we know we've populated.
* *
@@ -60,6 +33,14 @@ namespace Targets
*/ */
std::map<TargetMemoryAddress, TargetMemoryAddress> populatedSegments = {}; std::map<TargetMemoryAddress, TargetMemoryAddress> populatedSegments = {};
/**
* Tracks a newly populated segment.
*
* @param startAddress
* @param endAddress
*/
void trackSegment(TargetMemoryAddress startAddress, TargetMemoryAddress endAddress);
/** /**
* Finds the segment that intersects with the given address. Segments cannot overlap, so only one segment can * Finds the segment that intersects with the given address. Segments cannot overlap, so only one segment can
* intersect with the given address, at any given time. * intersect with the given address, at any given time.