Program memory cache
This commit is contained in:
@@ -138,6 +138,10 @@ TargetConfig::TargetConfig(const YAML::Node& targetNode) {
|
|||||||
this->hardwareBreakpoints = targetNode["hardwareBreakpoints"].as<bool>(this->hardwareBreakpoints);
|
this->hardwareBreakpoints = targetNode["hardwareBreakpoints"].as<bool>(this->hardwareBreakpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (targetNode["programMemoryCache"]) {
|
||||||
|
this->programMemoryCache = targetNode["programMemoryCache"].as<bool>(this->programMemoryCache);
|
||||||
|
}
|
||||||
|
|
||||||
this->targetNode = targetNode;
|
this->targetNode = targetNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ struct TargetConfig
|
|||||||
*/
|
*/
|
||||||
bool hardwareBreakpoints = true;
|
bool hardwareBreakpoints = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether Bloom will employ a cache for the target's program memory.
|
||||||
|
*/
|
||||||
|
bool programMemoryCache = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For extracting any target specific configuration. See Avr8TargetConfig::Avr8TargetConfig() and
|
* For extracting any target specific configuration. See Avr8TargetConfig::Avr8TargetConfig() and
|
||||||
* Avr8::preActivationConfigure() for an example of this.
|
* Avr8::preActivationConfigure() for an example of this.
|
||||||
|
|||||||
@@ -517,6 +517,10 @@ namespace TargetController
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->programMemoryCache = std::make_unique<Targets::TargetMemoryCache>(
|
||||||
|
targetDescriptor.memoryDescriptorsByType.at(targetDescriptor.programMemoryType)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TargetControllerComponent::releaseHardware() {
|
void TargetControllerComponent::releaseHardware() {
|
||||||
@@ -795,6 +799,42 @@ namespace TargetController
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<TargetMemoryRead> TargetControllerComponent::handleReadTargetMemory(ReadTargetMemory& command) {
|
std::unique_ptr<TargetMemoryRead> TargetControllerComponent::handleReadTargetMemory(ReadTargetMemory& command) {
|
||||||
|
const auto& targetDescriptor = this->getTargetDescriptor();
|
||||||
|
if (
|
||||||
|
command.memoryType == targetDescriptor.programMemoryType
|
||||||
|
&& this->environmentConfig.targetConfig.programMemoryCache
|
||||||
|
) {
|
||||||
|
assert(this->programMemoryCache);
|
||||||
|
|
||||||
|
|
||||||
|
if (!this->programMemoryCache->contains(command.startAddress, command.bytes)) {
|
||||||
|
Logger::debug(
|
||||||
|
"Program memory cache miss at 0x" + Services::StringService::toHex(command.startAddress) + ", "
|
||||||
|
+ std::to_string(command.bytes) + " bytes"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: We're currently ignoring command.excludedAddressRanges when populating the program
|
||||||
|
* memory cache. This isn't a big deal, so I'll sort it later.
|
||||||
|
*/
|
||||||
|
this->programMemoryCache->insert(
|
||||||
|
command.startAddress,
|
||||||
|
this->target->readMemory(
|
||||||
|
command.memoryType,
|
||||||
|
command.startAddress,
|
||||||
|
std::max(
|
||||||
|
command.bytes,
|
||||||
|
targetDescriptor.memoryDescriptorsByType.at(command.memoryType).pageSize.value_or(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<TargetMemoryRead>(
|
||||||
|
this->programMemoryCache->fetch(command.startAddress, command.bytes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return std::make_unique<TargetMemoryRead>(
|
return std::make_unique<TargetMemoryRead>(
|
||||||
command.bytes > 0
|
command.bytes > 0
|
||||||
? this->target->readMemory(
|
? this->target->readMemory(
|
||||||
@@ -819,6 +859,14 @@ namespace TargetController
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->target->writeMemory(command.memoryType, bufferStartAddress, buffer);
|
this->target->writeMemory(command.memoryType, bufferStartAddress, buffer);
|
||||||
|
|
||||||
|
if (
|
||||||
|
command.memoryType == targetDescriptor.programMemoryType
|
||||||
|
&& this->environmentConfig.targetConfig.programMemoryCache
|
||||||
|
) {
|
||||||
|
this->programMemoryCache->insert(bufferStartAddress, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
EventManager::triggerEvent(
|
EventManager::triggerEvent(
|
||||||
std::make_shared<Events::MemoryWrittenToTarget>(command.memoryType, bufferStartAddress, bufferSize)
|
std::make_shared<Events::MemoryWrittenToTarget>(command.memoryType, bufferStartAddress, bufferSize)
|
||||||
);
|
);
|
||||||
@@ -866,11 +914,17 @@ namespace TargetController
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Response> TargetControllerComponent::handleEraseTargetMemory(EraseTargetMemory& command) {
|
std::unique_ptr<Response> TargetControllerComponent::handleEraseTargetMemory(EraseTargetMemory& command) {
|
||||||
if (
|
if (command.memoryType == this->getTargetDescriptor().programMemoryType) {
|
||||||
command.memoryType == this->getTargetDescriptor().programMemoryType
|
if (!this->target->programmingModeEnabled()) {
|
||||||
&& !this->target->programmingModeEnabled()
|
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) {
|
||||||
|
assert(this->programMemoryCache);
|
||||||
|
|
||||||
|
Logger::debug("Clearing program memory cache");
|
||||||
|
this->programMemoryCache->clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->target->eraseMemory(command.memoryType);
|
this->target->eraseMemory(command.memoryType);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
#include "src/Targets/Targets.hpp"
|
#include "src/Targets/Targets.hpp"
|
||||||
#include "src/Targets/TargetRegister.hpp"
|
#include "src/Targets/TargetRegister.hpp"
|
||||||
#include "src/Targets/TargetMemory.hpp"
|
#include "src/Targets/TargetMemory.hpp"
|
||||||
|
#include "src/Targets/TargetMemoryCache.hpp"
|
||||||
|
|
||||||
#include "src/EventManager/EventManager.hpp"
|
#include "src/EventManager/EventManager.hpp"
|
||||||
#include "src/EventManager/EventListener.hpp"
|
#include "src/EventManager/EventListener.hpp"
|
||||||
@@ -168,6 +169,16 @@ namespace TargetController
|
|||||||
std::map<Targets::TargetMemoryAddress, Targets::TargetBreakpoint> softwareBreakpointsByAddress;
|
std::map<Targets::TargetMemoryAddress, Targets::TargetBreakpoint> softwareBreakpointsByAddress;
|
||||||
std::map<Targets::TargetMemoryAddress, Targets::TargetBreakpoint> hardwareBreakpointsByAddress;
|
std::map<Targets::TargetMemoryAddress, Targets::TargetBreakpoint> hardwareBreakpointsByAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target's program memory cache.
|
||||||
|
*
|
||||||
|
* If program caching is enabled, all program memory reads will be serviced by the cache, if we have the data.
|
||||||
|
*
|
||||||
|
* We use std::unique_ptr here due to delayed construction (we construct this after activating the target
|
||||||
|
* and obtaining the target descriptor).
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Targets::TargetMemoryCache> programMemoryCache = nullptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ target_sources(
|
|||||||
PRIVATE
|
PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription/TargetDescriptionFile.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TargetDescription/TargetDescriptionFile.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TargetRegister.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/TargetRegister.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TargetMemoryCache.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/Avr8.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/Avr8.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/Avr8TargetConfig.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/Avr8TargetConfig.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/PhysicalInterface.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Microchip/AVR/AVR8/PhysicalInterface.cpp
|
||||||
|
|||||||
125
src/Targets/TargetMemoryCache.cpp
Normal file
125
src/Targets/TargetMemoryCache.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "TargetMemoryCache.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "src/Exceptions/Exception.hpp"
|
||||||
|
|
||||||
|
namespace Targets
|
||||||
|
{
|
||||||
|
TargetMemoryCache::TargetMemoryCache(const TargetMemoryDescriptor& memoryDescriptor)
|
||||||
|
: memoryDescriptor(memoryDescriptor)
|
||||||
|
, data(TargetMemoryBuffer(memoryDescriptor.size(), 0x00))
|
||||||
|
{}
|
||||||
|
|
||||||
|
TargetMemoryBuffer TargetMemoryCache::fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const {
|
||||||
|
const auto startIndex = startAddress - this->memoryDescriptor.addressRange.startAddress;
|
||||||
|
|
||||||
|
if (
|
||||||
|
startAddress < this->memoryDescriptor.addressRange.startAddress
|
||||||
|
|| (startIndex + bytes) > this->data.size()
|
||||||
|
) {
|
||||||
|
throw Exceptions::Exception("Invalid cache access");
|
||||||
|
}
|
||||||
|
|
||||||
|
return TargetMemoryBuffer(this->data.begin() + startIndex, this->data.begin() + startIndex + bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TargetMemoryCache::contains(TargetMemoryAddress startAddress, TargetMemorySize bytes) const {
|
||||||
|
const auto intersectingSegmentIt = this->intersectingSegment(startAddress);
|
||||||
|
|
||||||
|
return
|
||||||
|
intersectingSegmentIt != this->populatedSegments.end()
|
||||||
|
&& intersectingSegmentIt->first <= startAddress
|
||||||
|
&& intersectingSegmentIt->second >= (startAddress + bytes - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetMemoryCache::insert(TargetMemoryAddress startAddress, const TargetMemoryBuffer& data) {
|
||||||
|
const auto startIndex = startAddress - this->memoryDescriptor.addressRange.startAddress;
|
||||||
|
|
||||||
|
std::copy(data.begin(), data.end(), this->data.begin() + startIndex);
|
||||||
|
|
||||||
|
const auto endAddress = static_cast<Targets::TargetMemoryAddress>(startAddress + data.size() - 1);
|
||||||
|
|
||||||
|
const auto intersectingStartSegmentIt = this->intersectingSegment(startAddress);
|
||||||
|
const auto intersectingEndSegmentIt = this->intersectingSegment(endAddress);
|
||||||
|
|
||||||
|
if (
|
||||||
|
intersectingStartSegmentIt == this->populatedSegments.end()
|
||||||
|
&& intersectingEndSegmentIt == this->populatedSegments.end()
|
||||||
|
) {
|
||||||
|
this->populatedSegments.insert(std::pair(startAddress, endAddress));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersectingStartSegmentIt == intersectingEndSegmentIt) {
|
||||||
|
// We already have a populated segment containing this address range. Nothing to do here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newStartSegment = std::optional<decltype(this->populatedSegments)::value_type>();
|
||||||
|
auto newEndSegment = std::optional<decltype(this->populatedSegments)::value_type>();
|
||||||
|
|
||||||
|
if (intersectingStartSegmentIt != this->populatedSegments.end()) {
|
||||||
|
newStartSegment.emplace(intersectingStartSegmentIt->first, endAddress);
|
||||||
|
this->populatedSegments.erase(intersectingStartSegmentIt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersectingEndSegmentIt != this->populatedSegments.end()) {
|
||||||
|
newEndSegment.emplace(startAddress, intersectingEndSegmentIt->second);
|
||||||
|
this->populatedSegments.erase(intersectingEndSegmentIt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStartSegment.has_value() && newEndSegment.has_value()) {
|
||||||
|
// The two new segments overlap. Merge them into one and remove any segments between them
|
||||||
|
newStartSegment.emplace(newStartSegment->first, newEndSegment->second);
|
||||||
|
newEndSegment.reset();
|
||||||
|
|
||||||
|
for (
|
||||||
|
auto segmentIt = this->populatedSegments.upper_bound(newStartSegment->first);
|
||||||
|
segmentIt != this->populatedSegments.end();
|
||||||
|
) {
|
||||||
|
if (segmentIt->second > newStartSegment->second) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->populatedSegments.erase(segmentIt++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStartSegment.has_value()) {
|
||||||
|
this->populatedSegments.insert(*newStartSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newEndSegment.has_value()) {
|
||||||
|
this->populatedSegments.insert(*newEndSegment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetMemoryCache::clear() {
|
||||||
|
this->populatedSegments.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetMemoryCache::SegmentIt TargetMemoryCache::intersectingSegment(TargetMemoryAddress address) const {
|
||||||
|
if (this->populatedSegments.empty()) {
|
||||||
|
return this->populatedSegments.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lowerBoundSegmentIt = this->populatedSegments.lower_bound(address);
|
||||||
|
|
||||||
|
if (lowerBoundSegmentIt != this->populatedSegments.end()) {
|
||||||
|
if (lowerBoundSegmentIt->first != address && lowerBoundSegmentIt != this->populatedSegments.begin()) {
|
||||||
|
--lowerBoundSegmentIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowerBoundSegmentIt->first <= address && lowerBoundSegmentIt->second >= address
|
||||||
|
? lowerBoundSegmentIt
|
||||||
|
: this->populatedSegments.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the segments precede the given address. If the last segment doesn't intersect, none of them will.
|
||||||
|
const auto lastSegment = std::prev(this->populatedSegments.end());
|
||||||
|
return lastSegment->first <= address && lastSegment->second >= address
|
||||||
|
? lastSegment
|
||||||
|
: this->populatedSegments.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/Targets/TargetMemoryCache.hpp
Normal file
72
src/Targets/TargetMemoryCache.hpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "TargetMemory.hpp"
|
||||||
|
|
||||||
|
namespace Targets
|
||||||
|
{
|
||||||
|
class TargetMemoryCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TargetMemoryCache(const TargetMemoryDescriptor& memoryDescriptor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches data from the cache.
|
||||||
|
*
|
||||||
|
* @param startAddress
|
||||||
|
* @param bytes
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TargetMemoryBuffer fetch(TargetMemoryAddress startAddress, TargetMemorySize bytes) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the cache currently holds data within the given address range.
|
||||||
|
*
|
||||||
|
* @param startAddress
|
||||||
|
* @param bytes
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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, const TargetMemoryBuffer& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the cache.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const TargetMemoryDescriptor& memoryDescriptor;
|
||||||
|
TargetMemoryBuffer data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A populated segment is just an address range in the cache that we know we've populated.
|
||||||
|
*
|
||||||
|
* populatedSegments::value_type::first = The start address (inclusive) of the populated range
|
||||||
|
* populatedSegments::value_type::second = The end address (inclusive) of the populated range
|
||||||
|
*/
|
||||||
|
std::map<TargetMemoryAddress, TargetMemoryAddress> populatedSegments = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @param address
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* An iterator to the intersecting segment, or populatedSegments::end() if none is found.
|
||||||
|
*/
|
||||||
|
using SegmentIt = decltype(TargetMemoryCache::populatedSegments)::const_iterator;
|
||||||
|
SegmentIt intersectingSegment(TargetMemoryAddress address) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user