You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
6.7 KiB
202 lines
6.7 KiB
/* PlipMemoryMap.cpp |
|
* |
|
* A flexible memory mapper. |
|
*/ |
|
|
|
#include <tuple> |
|
|
|
#include "PlipMemoryMap.h" |
|
|
|
namespace Plip { |
|
void PlipMemoryMap::AddBlock(PlipMemory *memory, uint32_t offset) { |
|
AddBlock(memory, offset, memory->GetLength() - offset); |
|
} |
|
|
|
void PlipMemoryMap::AddBlock(PlipMemory *memory, uint32_t offset, uint32_t length) { |
|
uint32_t start = 0; |
|
|
|
if(m_rangeList.begin() != m_rangeList.end()) { |
|
auto last = m_rangeList.back(); |
|
start = last.startAddress + last.length; |
|
} |
|
|
|
m_rangeList.push_back({ |
|
.startAddress = start, |
|
.memory = memory, |
|
.offset = offset, |
|
.length = length |
|
}); |
|
|
|
UpdateVector(); |
|
} |
|
|
|
void PlipMemoryMap::AssignBlock(PlipMemory *memory, uint32_t address, uint32_t offset) { |
|
AssignBlock(memory, address, offset, memory->GetLength() - offset); |
|
} |
|
|
|
void PlipMemoryMap::AssignBlock(PlipMemory *memory, uint32_t address, uint32_t offset, uint32_t length) { |
|
UnassignBlock(address, length); |
|
AssignBlockDirect(memory, address, offset, length); |
|
} |
|
|
|
void PlipMemoryMap::AssignBlockDirect(PlipMemory *memory, uint32_t address, uint32_t offset, uint32_t length) { |
|
auto it = m_rangeList.begin(); |
|
auto end = m_rangeList.end(); |
|
|
|
while(it != end) { |
|
if(it->startAddress > address) break; |
|
it++; |
|
} |
|
|
|
m_rangeList.insert(it, { |
|
.startAddress = address, |
|
.memory = memory, |
|
.offset = offset, |
|
.length = length |
|
}); |
|
|
|
UpdateVector(); |
|
} |
|
|
|
void PlipMemoryMap::ClearLastRead() { |
|
m_lastRead.address = 0; |
|
m_lastRead.value = 0; |
|
} |
|
|
|
void PlipMemoryMap::ClearLastWrite() { |
|
m_lastWritten.address = 0; |
|
m_lastWritten.value = 0; |
|
} |
|
|
|
uint8_t PlipMemoryMap::GetByte(uint32_t address, bool privileged) { |
|
auto block = FindAddress(address); |
|
|
|
if(block.memory == nullptr) return 0; |
|
|
|
m_lastRead.address = address; |
|
m_lastRead.value = block.memory->GetByte(block.offset, privileged); |
|
return m_lastRead.value; |
|
} |
|
|
|
PlipMemoryValue PlipMemoryMap::GetLastRead() { |
|
return m_lastRead; |
|
} |
|
|
|
PlipMemoryValue PlipMemoryMap::GetLastWrite() { |
|
return m_lastWritten; |
|
} |
|
|
|
uint32_t PlipMemoryMap::GetLength() { |
|
return m_range.crbegin()->startAddress + m_range.crbegin()->memory->GetLength(); |
|
} |
|
|
|
PlipMemoryMap::BlockRangeResult PlipMemoryMap::IsBlockInRange( |
|
const PlipMemoryMapRange &block, uint32_t startAddress, uint32_t endAddress) { |
|
auto blockEnd = block.startAddress + block.length - 1; |
|
|
|
if(block.startAddress >= startAddress && blockEnd <= endAddress) |
|
return CompletelyInRange; |
|
else if(block.startAddress > endAddress || blockEnd < startAddress) |
|
return NotInRange; |
|
|
|
return PartiallyInRange; |
|
} |
|
|
|
void PlipMemoryMap::SetByte(uint32_t address, uint8_t value, bool privileged) { |
|
auto block = FindAddress(address); |
|
|
|
if(block.memory == nullptr) return; |
|
block.memory->SetByte(block.offset, value, privileged); |
|
m_lastWritten.address = address; |
|
m_lastWritten.value = value; |
|
} |
|
|
|
void PlipMemoryMap::SetUnprivilegedValue(uint8_t value) { |
|
for(auto block : m_range) |
|
block.memory->SetUnprivilegedValue(value); |
|
} |
|
|
|
void PlipMemoryMap::UnassignBlock(uint32_t address, uint32_t length) { |
|
/* This function must handle the following cases: |
|
* |
|
* 1. Handle empty, unmapped areas of memory. |
|
* 2. Remove blocks entirely if necessary. |
|
* 3. Adjust offsets/lengths for clipped blocks. |
|
* 4. Potentially handle multiple blocks at once. |
|
* 5. Split blocks in half if necessary. |
|
*/ |
|
|
|
auto endAddress = address + length - 1; // end address |
|
|
|
auto it = m_rangeList.begin(); |
|
auto end = m_rangeList.end(); |
|
auto foundBlock = false; |
|
|
|
while(it != end) { |
|
switch(IsBlockInRange(*it, address, endAddress)) { |
|
case NotInRange: |
|
if(foundBlock) { |
|
// We're now out of range. It's safe to abort. |
|
it = end; |
|
continue; |
|
} |
|
|
|
// Block is not in range. Ignore. |
|
it++; |
|
continue; |
|
|
|
case CompletelyInRange: |
|
// This block can be removed. |
|
foundBlock = true; |
|
it = m_rangeList.erase(it); |
|
continue; |
|
|
|
case PartiallyInRange: |
|
// Remove the portion of the block that overlaps. |
|
foundBlock = true; |
|
auto blockEnd = it->startAddress + it->length - 1; |
|
|
|
if(address <= it->startAddress) { |
|
// Unassign range overlaps the beginning of the memory |
|
// block. Move the starting address and offset forward, |
|
// and bump the length back. |
|
auto adjust = endAddress - it->startAddress + 1; |
|
it->startAddress += adjust; |
|
it->offset += adjust; |
|
it->length -= adjust; |
|
} else if(address > it->startAddress && endAddress >= blockEnd) { |
|
// Unassign range overlaps the end of the memory block. |
|
// Pull the block length back. |
|
it->length -= endAddress - address + 1; |
|
} else { |
|
// Unassign range falls in the middle of this memory |
|
// block. Truncate the block, then push in a new block |
|
// with an adjusted offset/length. |
|
it->length = address - it->startAddress; |
|
|
|
auto newStart = endAddress + 1; |
|
auto newLength = blockEnd - endAddress; |
|
auto offset = endAddress - it->startAddress + 1; |
|
AssignBlockDirect(it->memory, newStart, offset, newLength); |
|
|
|
// There's no way there are any block beyond this, so |
|
// break here. |
|
it = end; |
|
break; |
|
} |
|
|
|
it++; |
|
continue; |
|
} |
|
} |
|
|
|
UpdateVector(); |
|
} |
|
|
|
void PlipMemoryMap::UpdateVector() { |
|
m_range.clear(); |
|
for(auto const &range : m_rangeList) |
|
m_range.push_back(range); |
|
m_rangeCount = m_range.size(); |
|
} |
|
}
|
|
|