Files
BloomPatched/src/Targets/Microchip/Avr8/Avr8.cpp

1197 lines
51 KiB
C++
Raw Normal View History

#include "Avr8.hpp"
#include <cassert>
#include <bitset>
#include <limits>
#include <thread>
#include <algorithm>
#include <optional>
#include <functional>
#include <unordered_map>
#include "IspParameters.hpp"
#include "src/Logger/Logger.hpp"
#include "src/Services/PathService.hpp"
#include "src/Services/StringService.hpp"
#include "src/Exceptions/InvalidConfig.hpp"
#include "Exceptions/DebugWirePhysicalInterfaceError.hpp"
namespace Targets::Microchip::Avr8
{
using namespace Exceptions;
Avr8::Avr8(const TargetConfig& targetConfig, TargetDescriptionFile&& targetDescriptionFile)
2024-10-06 18:10:02 +01:00
: targetConfig(Avr8TargetConfig{targetConfig})
, targetDescriptionFile(std::move(targetDescriptionFile))
, programAddressSpaceDescriptor(this->targetDescriptionFile.getProgramAddressSpaceDescriptor())
, dataAddressSpaceDescriptor(this->targetDescriptionFile.getDataAddressSpaceDescriptor())
, fuseAddressSpaceDescriptor(this->targetDescriptionFile.getFuseAddressSpaceDescriptor())
, programMemorySegmentDescriptor(this->targetDescriptionFile.getProgramMemorySegmentDescriptor())
, ramMemorySegmentDescriptor(this->targetDescriptionFile.getRamMemorySegmentDescriptor())
, ioMemorySegmentDescriptor(this->targetDescriptionFile.getIoMemorySegmentDescriptor())
, fuseMemorySegmentDescriptor(this->targetDescriptionFile.getFuseMemorySegmentDescriptor())
, signature(this->targetDescriptionFile.getTargetSignature())
, family(this->targetDescriptionFile.getAvrFamily())
, physicalInterfaces(this->targetDescriptionFile.getPhysicalInterfaces())
, gpioPortPeripheralDescriptors(this->targetDescriptionFile.gpioPortPeripheralDescriptors())
, gpioPadDescriptorsByPadId(Avr8::generateGpioPadDescriptorMapping(this->gpioPortPeripheralDescriptors))
, fuseEnableStrategy(this->targetDescriptionFile.getFuseEnableStrategy().value_or(FuseEnableStrategy::CLEAR))
{
const auto cpuPeripheralDescriptor = this->targetDescriptionFile.getTargetPeripheralDescriptor("cpu");
const auto& cpuRegisterGroup = cpuPeripheralDescriptor.getRegisterGroupDescriptor("cpu");
const auto spDescriptor = cpuRegisterGroup.tryGetRegisterDescriptor("sp");
if (spDescriptor.has_value()) {
this->spRegisterDescriptor.emplace(spDescriptor->get().clone());
}
const auto spLowDescriptor = cpuRegisterGroup.tryGetRegisterDescriptor("spl");
if (spLowDescriptor.has_value()) {
this->spLowRegisterDescriptor.emplace(spLowDescriptor->get().clone());
}
const auto spHighDescriptor = cpuRegisterGroup.tryGetRegisterDescriptor("sph");
if (spHighDescriptor.has_value()) {
this->spHighRegisterDescriptor.emplace(spHighDescriptor->get().clone());
}
if (!this->physicalInterfaces.contains(this->targetConfig.physicalInterface)) {
/*
* The user has selected a physical interface that does not appear to be supported by the selected
* target.
*
* Bloom's target description files provide a list of supported physical interfaces for each target
* (which is how this->physicalInterfaces is populated), but it's possible that this list may
* be wrong/incomplete. For this reason, we don't throw an exception here. Instead, we just present the
* user with a warning and a list of physical interfaces known to be supported by their selected target.
*
* If the target truly doesn't'support the physical interface, an exception will be thrown during
* activation.
*/
const auto physicalInterfaceNames = getPhysicalInterfaceNames();
const auto supportedPhysicalInterfaceList = std::accumulate(
this->physicalInterfaces.begin(),
this->physicalInterfaces.end(),
std::string{},
[&physicalInterfaceNames] (const std::string& string, TargetPhysicalInterface physicalInterface) {
if (physicalInterface == TargetPhysicalInterface::ISP) {
/*
* Don't include the ISP interface in the list of supported interfaces, as doing so may
* mislead the user into thinking the ISP interface can be used for debugging operations.
*/
return string;
}
return string + "\n - " + physicalInterfaceNames.at(physicalInterface);
}
);
Logger::warning(
"\nThe selected target does not support the selected physical interface ("
+ physicalInterfaceNames.at(this->targetConfig.physicalInterface) + "). Target activation "
"will likely fail. The target supports the following physical interfaces: \n"
+ supportedPhysicalInterfaceList + "\n\nFor physical interface configuration values, see "
+ Services::PathService::homeDomainName() + "/docs/configuration/target-physical-interfaces."
);
}
if (
this->targetConfig.manageOcdenFuseBit
&& this->targetConfig.physicalInterface != TargetPhysicalInterface::JTAG
) {
Logger::warning(
2024-12-14 16:17:54 +00:00
"The 'manage_ocden_fuse_bit' parameter only applies to JTAG targets. It will be ignored in this session."
);
}
}
bool Avr8::supportsDebugTool(DebugTool* debugTool) {
2024-12-24 19:58:48 +00:00
return debugTool->getAvr8DebugInterface(this->targetDescriptionFile, this->targetConfig) != nullptr;
}
void Avr8::setDebugTool(DebugTool* debugTool) {
this->targetPowerManagementInterface = debugTool->getTargetPowerManagementInterface();
this->avr8DebugInterface = debugTool->getAvr8DebugInterface(this->targetDescriptionFile, this->targetConfig);
if (this->physicalInterfaces.contains(TargetPhysicalInterface::ISP)) {
this->avrIspInterface = debugTool->getAvrIspInterface(this->targetDescriptionFile, this->targetConfig);
if (
this->avrIspInterface == nullptr
&& this->targetConfig.manageDwenFuseBit
&& this->targetConfig.physicalInterface == TargetPhysicalInterface::DEBUG_WIRE
) {
Logger::warning(
"The connected debug tool (or associated driver) does not provide any ISP interface. "
"Bloom will be unable to manage the DWEN fuse bit."
);
}
}
}
void Avr8::activate() {
if (this->activated) {
return;
}
this->avr8DebugInterface->init();
try {
this->avr8DebugInterface->activate();
} catch (const Exceptions::DebugWirePhysicalInterfaceError& debugWireException) {
// We failed to activate the debugWIRE physical interface. DWEN fuse bit may need updating.
if (!this->targetConfig.manageDwenFuseBit) {
throw TargetOperationFailure{
"Failed to activate debugWIRE physical interface - check target connection and DWEN fuse "
"bit. Bloom can manage the DWEN fuse bit automatically. For instructions on enabling this "
"function, see " + Services::PathService::homeDomainName() + "/docs/debugging-avr-debugwire"
};
}
try {
Logger::warning(
"Failed to activate the debugWIRE physical interface - attempting to access target via "
"the ISP interface, for DWEN fuse bit inspection."
);
this->updateDwenFuseBit(true);
// If the debug tool provides a TargetPowerManagementInterface, attempt to cycle the target power
if (
this->targetPowerManagementInterface != nullptr
&& this->targetConfig.cycleTargetPowerPostDwenUpdate
) {
Logger::info("Cycling target power");
Logger::debug("Disabling target power");
this->targetPowerManagementInterface->disableTargetPower();
Logger::debug(
"Holding power off for ~" + std::to_string(this->targetConfig.targetPowerCycleDelay.count())
+ " ms"
);
std::this_thread::sleep_for(this->targetConfig.targetPowerCycleDelay);
Logger::debug("Enabling target power");
this->targetPowerManagementInterface->enableTargetPower();
Logger::debug(
"Waiting ~" + std::to_string(this->targetConfig.targetPowerCycleDelay.count())
+ " ms for target power-up"
);
std::this_thread::sleep_for(this->targetConfig.targetPowerCycleDelay);
}
} catch (const Exception& exception) {
throw Exception{
"Failed to access/update DWEN fuse bit via ISP interface - " + exception.getMessage()
};
}
Logger::info("Retrying debugWIRE physical interface activation");
this->avr8DebugInterface->activate();
}
this->stop();
this->reset();
if (
this->targetConfig.physicalInterface == TargetPhysicalInterface::JTAG
&& this->targetConfig.manageOcdenFuseBit
) {
Logger::debug("Attempting OCDEN fuse bit management");
this->updateOcdenFuseBit(true);
}
this->activated = true;
if (this->targetConfig.signatureValidation) {
/*
* Validate the target signature.
*
* The signature obtained from the device should match what we loaded from the target description file.
*/
const auto targetSignature = this->avr8DebugInterface->getDeviceId();
if (targetSignature != this->signature) {
throw Exception{
"Failed to validate connected target - target signature mismatch.\nThe target signature"
" (\"" + targetSignature.toHex() +
"\") does not match the AVR8 target description signature (\""
+ this->signature.toHex() + "\"). This will likely be due to an incorrect target name in the "
+ "configuration file (bloom.yaml)."
};
}
}
}
void Avr8::deactivate() {
try {
if (this->avr8DebugInterface == nullptr) {
return;
}
this->stop();
if (
this->targetConfig.physicalInterface == TargetPhysicalInterface::JTAG
&& this->targetConfig.manageOcdenFuseBit
) {
Logger::debug("Attempting OCDEN fuse bit management");
this->updateOcdenFuseBit(false);
} else {
this->avr8DebugInterface->deactivate();
}
this->activated = false;
} catch (const Exception& exception) {
Logger::error("Failed to deactivate AVR8 target - " + exception.getMessage());
}
}
void Avr8::postActivate() {
Logger::info("AVR signature: " + this->avr8DebugInterface->getDeviceId().toHex());
}
TargetDescriptor Avr8::targetDescriptor() {
auto descriptor = TargetDescriptor{
this->targetDescriptionFile.getName(),
this->targetDescriptionFile.getFamily(),
this->signature.toHex(),
this->targetDescriptionFile.tryGetVendorName().value_or("Microchip"),
this->targetDescriptionFile.targetAddressSpaceDescriptorsByKey(),
this->targetDescriptionFile.targetPeripheralDescriptorsByKey(),
this->targetDescriptionFile.targetPadDescriptorsByKey(),
this->targetDescriptionFile.targetPinoutDescriptorsByKey(),
2024-08-19 19:43:02 +01:00
this->targetDescriptionFile.targetVariantDescriptorsByKey(),
this->getBreakpointResources()
};
/*
* General purpose CPU registers are not included in AVR8 TDFs, so we manually add them to the target
* descriptor here.
*/
const auto& registerFileAddressSpace = this->targetDescriptionFile.getRegisterFileAddressSpace();
const auto& registerFileMemorySegment = registerFileAddressSpace.getMemorySegment("gp_registers");
auto& cpuPeripheral = descriptor.getPeripheralDescriptor("cpu");
auto& gpRegisterGroup = cpuPeripheral.registerGroupDescriptorsByKey.emplace(
"gpr",
TargetRegisterGroupDescriptor{
"gpr",
"gpr",
"GPR",
cpuPeripheral.key,
registerFileAddressSpace.key,
std::nullopt,
{},
{}
}
).first->second;
for (auto i = std::uint8_t{0}; i <= 31; ++i) {
const auto key = "r" + std::to_string(i);
gpRegisterGroup.registerDescriptorsByKey.emplace(
key,
TargetRegisterDescriptor{
key,
"R" + std::to_string(i),
gpRegisterGroup.absoluteKey,
cpuPeripheral.key,
registerFileAddressSpace.key,
registerFileMemorySegment.startAddress + i,
1,
TargetRegisterType::GENERAL_PURPOSE_REGISTER,
2024-12-24 19:58:48 +00:00
TargetRegisterAccess{true, true},
std::nullopt,
{}
}
);
}
2024-08-17 12:44:01 +01:00
// The debug interface may have its own access restrictions for registers.
for (auto& [peripheralKey, peripheral] : descriptor.peripheralDescriptorsByKey) {
for (auto& [groupKey, registerGroup] : peripheral.registerGroupDescriptorsByKey) {
this->applyDebugInterfaceAccessRestrictions(
registerGroup,
descriptor.getAddressSpaceDescriptor(registerGroup.addressSpaceKey)
);
}
}
for (auto& [addressSpaceKey, addressSpaceDescriptor] : descriptor.addressSpaceDescriptorsByKey) {
for (auto& [segmentKey, segmentDescriptor] : addressSpaceDescriptor.segmentDescriptorsByKey) {
if (segmentDescriptor.type == TargetMemorySegmentType::FLASH) {
segmentDescriptor.debugModeAccess.writeable = false;
}
}
}
// Make RAM, FLASH and EEPROM available for inspection the Insight GUI.
descriptor.getMemorySegmentDescriptor("data", "internal_ram").inspectionEnabled = true;
descriptor.getMemorySegmentDescriptor("prog", "internal_program_memory").inspectionEnabled = true;
descriptor.getMemorySegmentDescriptor(
descriptor.getFirstAddressSpaceDescriptorContainingMemorySegment("internal_eeprom").key,
"internal_eeprom"
).inspectionEnabled = true;
return descriptor;
}
void Avr8::run(std::optional<TargetMemoryAddress> toAddress) {
if (toAddress.has_value()) {
return this->avr8DebugInterface->runTo(*toAddress);
}
this->avr8DebugInterface->run();
}
void Avr8::stop() {
this->avr8DebugInterface->stop();
}
void Avr8::step() {
this->avr8DebugInterface->step();
}
void Avr8::reset() {
this->avr8DebugInterface->reset();
}
void Avr8::setProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
if (breakpoint.addressSpaceDescriptor != this->programAddressSpaceDescriptor) {
throw Exception{"Unexpected address space"};
}
this->avr8DebugInterface->setProgramBreakpoint(breakpoint);
}
void Avr8::removeProgramBreakpoint(const TargetProgramBreakpoint& breakpoint) {
if (breakpoint.addressSpaceDescriptor != this->programAddressSpaceDescriptor) {
throw Exception{"Unexpected address space"};
}
this->avr8DebugInterface->removeProgramBreakpoint(breakpoint);
}
TargetRegisterDescriptorAndValuePairs Avr8::readRegisters(const Targets::TargetRegisterDescriptors& descriptors) {
return this->avr8DebugInterface->readRegisters(descriptors);
}
void Avr8::writeRegisters(const TargetRegisterDescriptorAndValuePairs& registers) {
this->avr8DebugInterface->writeRegisters(registers);
}
TargetMemoryBuffer Avr8::readMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
TargetMemoryAddress startAddress,
TargetMemorySize bytes,
const std::set<Targets::TargetMemoryAddressRange>& excludedAddressRanges
) {
return this->avr8DebugInterface->readMemory(
addressSpaceDescriptor,
memorySegmentDescriptor,
startAddress,
bytes,
excludedAddressRanges
);
}
void Avr8::writeMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
std::uint32_t startAddress,
TargetMemoryBufferSpan buffer
) {
if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH && !this->programmingModeEnabled()) {
throw Exception{"Attempted Flash memory write in the absence of an active programming session."};
}
this->avr8DebugInterface->writeMemory(addressSpaceDescriptor, memorySegmentDescriptor, startAddress, buffer);
}
bool Avr8::isProgramMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor,
TargetMemoryAddress startAddress,
TargetMemorySize size
) {
/*
* On AVR8 targets, memory segments that are marked as executable are executable in their entirety.
* No need for more granular checks here.
*/
return memorySegmentDescriptor.executable;
}
void Avr8::eraseMemory(
const TargetAddressSpaceDescriptor& addressSpaceDescriptor,
const TargetMemorySegmentDescriptor& memorySegmentDescriptor
) {
if (memorySegmentDescriptor.type == TargetMemorySegmentType::FLASH) {
if (!this->programmingModeEnabled()) {
throw Exception{"Attempted flash memory erase in the absence of an active programming session"};
}
if (this->targetConfig.physicalInterface == TargetPhysicalInterface::DEBUG_WIRE) {
// debugWIRE targets do not need to be erased
return;
}
/*
* To erase program memory on JTAG and UPDI targets, we must perform a chip erase. This means we could
* end up erasing EEPROM, unless the EESAVE fuse bit has been programmed.
*
* If configured to do so, we will ensure that the EESAVE fuse bit has been programmed before we perform
* the chip erase. The fuse will be restored to its original value at the end of the programming session.
*/
if (
this->targetConfig.physicalInterface == TargetPhysicalInterface::JTAG
|| this->targetConfig.physicalInterface == TargetPhysicalInterface::UPDI
) {
if (this->targetConfig.preserveEeprom) {
Logger::debug("Inspecting EESAVE fuse bit");
this->activeProgrammingSession->managingEesaveFuseBit = this->updateEesaveFuseBit(true);
} else {
Logger::warning(
"Performing chip-erase with preserveEeprom disabled. All EEPROM data will be lost!"
);
}
return this->avr8DebugInterface->eraseChip();
}
return this->avr8DebugInterface->eraseProgramMemory();
}
/*
* The debug interface does not have to support the erasing of RAM or EEPROM memory. We just implement this as
* a write operation.
*/
this->writeMemory(
addressSpaceDescriptor,
memorySegmentDescriptor,
memorySegmentDescriptor.addressRange.startAddress,
TargetMemoryBuffer(memorySegmentDescriptor.size(), 0xFF)
);
}
TargetExecutionState Avr8::getExecutionState() {
return this->avr8DebugInterface->getExecutionState();
}
TargetMemoryAddress Avr8::getProgramCounter() {
return this->avr8DebugInterface->getProgramCounter();
}
void Avr8::setProgramCounter(TargetMemoryAddress programCounter) {
this->avr8DebugInterface->setProgramCounter(programCounter);
}
TargetStackPointer Avr8::getStackPointer() {
auto descriptors = TargetRegisterDescriptors{};
if (this->spRegisterDescriptor.has_value()) {
descriptors.push_back(&*(this->spRegisterDescriptor));
}
if (this->spLowRegisterDescriptor.has_value()) {
descriptors.push_back(&*(this->spLowRegisterDescriptor));
}
if (this->spHighRegisterDescriptor.has_value()) {
descriptors.push_back(&*(this->spHighRegisterDescriptor));
}
auto output = TargetStackPointer{0};
for (const auto& [descriptor, value] : this->readRegisters(descriptors)) {
if (
this->spHighRegisterDescriptor.has_value()
&& descriptor.startAddress == this->spHighRegisterDescriptor->startAddress
) {
// SP high byte
assert(value.size() == 1);
output = (output & 0x000000FF) | static_cast<TargetStackPointer>(value[0] << 8);
} else {
assert(value.size() > 0 && value.size() <= 2);
for (auto i = std::size_t{0}; i < value.size(); ++i) {
output = (output << (8 * i)) | value[i];
}
}
}
return output;
}
void Avr8::setStackPointer(TargetStackPointer stackPointer) {
if (this->spRegisterDescriptor.has_value()) {
this->writeRegister(
*(this->spRegisterDescriptor),
this->spRegisterDescriptor->size > 1
? TargetMemoryBuffer({
static_cast<unsigned char>(stackPointer >> 8),
static_cast<unsigned char>(stackPointer)
})
: TargetMemoryBuffer({static_cast<unsigned char>(stackPointer)})
);
}
if (this->spLowRegisterDescriptor.has_value()) {
this->writeRegister(
*(this->spLowRegisterDescriptor),
TargetMemoryBuffer({static_cast<unsigned char>(stackPointer)})
);
}
if (this->spHighRegisterDescriptor.has_value()) {
this->writeRegister(
*(this->spHighRegisterDescriptor),
TargetMemoryBuffer({static_cast<unsigned char>(stackPointer >> 8)})
);
}
}
TargetGpioPadDescriptorAndStatePairs Avr8::getGpioPadStates(const TargetPadDescriptors& padDescriptors) {
auto output = TargetGpioPadDescriptorAndStatePairs{};
// To reduce the number of memory reads we perform here, we cache the data and map it by start address.
auto cachedRegsByStartAddress = std::unordered_map<TargetMemoryAddress, unsigned char>{};
const auto readGpioReg = [this, &cachedRegsByStartAddress] (const TargetRegisterDescriptor& descriptor) {
assert(descriptor.size == 1);
auto cachedRegIt = cachedRegsByStartAddress.find(descriptor.startAddress);
if (cachedRegIt == cachedRegsByStartAddress.end()) {
cachedRegIt = cachedRegsByStartAddress.emplace(
descriptor.startAddress,
this->readRegister(descriptor).at(0)
).first;
}
return cachedRegIt->second;
};
for (const auto* padDescriptor : padDescriptors) {
if (padDescriptor->type != TargetPadType::GPIO) {
continue;
}
const auto gpioPadDescriptorIt = this->gpioPadDescriptorsByPadId.find(padDescriptor->id);
if (gpioPadDescriptorIt == this->gpioPadDescriptorsByPadId.end()) {
continue;
}
const auto& gpioPadDescriptor = gpioPadDescriptorIt->second;
const auto ddrValue = (
readGpioReg(gpioPadDescriptor.dataDirectionRegisterDescriptor) & gpioPadDescriptor.registerMask
) != 0 ? TargetGpioPadState::DataDirection::OUTPUT : TargetGpioPadState::DataDirection::INPUT;
const auto& stateRegisterDescriptor = ddrValue == TargetGpioPadState::DataDirection::OUTPUT
? gpioPadDescriptor.outputRegisterDescriptor
: gpioPadDescriptor.inputRegisterDescriptor;
output.emplace_back(
2024-12-21 13:45:32 +00:00
*padDescriptor,
TargetGpioPadState{
.value = (readGpioReg(stateRegisterDescriptor) & gpioPadDescriptor.registerMask) != 0
2024-12-21 13:45:32 +00:00
? TargetGpioPadState::State::HIGH
: TargetGpioPadState::State::LOW,
.direction = ddrValue
}
);
}
return output;
}
void Avr8::setGpioPadState(const TargetPadDescriptor& padDescriptor, const TargetGpioPadState& state) {
using DataDirection = TargetGpioPadState::DataDirection;
using GpioState = TargetGpioPadState::State;
const auto gpioPadDescriptorIt = this->gpioPadDescriptorsByPadId.find(padDescriptor.id);
if (gpioPadDescriptorIt == this->gpioPadDescriptorsByPadId.end()) {
throw Exception{"Unknown pad"};
}
const auto& gpioPadDescriptor = gpioPadDescriptorIt->second;
const auto currentDdrValue = this->readRegister(gpioPadDescriptor.dataDirectionRegisterDescriptor).at(0);
this->writeRegister(
gpioPadDescriptor.dataDirectionRegisterDescriptor,
{
static_cast<unsigned char>(
state.direction == DataDirection::OUTPUT
? (currentDdrValue | gpioPadDescriptor.registerMask)
: (currentDdrValue & ~(gpioPadDescriptor.registerMask))
)
}
);
if (state.direction == DataDirection::OUTPUT) {
const auto currentOutputValue = this->readRegister(gpioPadDescriptor.outputRegisterDescriptor).at(0);
this->writeRegister(
gpioPadDescriptor.outputRegisterDescriptor,
{
static_cast<unsigned char>(
state.value == GpioState::HIGH
? (currentOutputValue | gpioPadDescriptor.registerMask)
: (currentOutputValue & ~(gpioPadDescriptor.registerMask))
)
}
);
}
}
void Avr8::enableProgrammingMode() {
if (this->activeProgrammingSession.has_value()) {
return;
}
this->avr8DebugInterface->clearAllBreakpoints();
this->avr8DebugInterface->enableProgrammingMode();
this->activeProgrammingSession = ProgrammingSession();
}
void Avr8::disableProgrammingMode() {
if (!this->activeProgrammingSession.has_value()) {
return;
}
if (this->activeProgrammingSession->managingEesaveFuseBit) {
this->updateEesaveFuseBit(false);
}
this->avr8DebugInterface->disableProgrammingMode();
this->stop();
this->activeProgrammingSession.reset();
}
bool Avr8::programmingModeEnabled() {
return this->activeProgrammingSession.has_value();
}
std::string Avr8::passthroughCommandHelpText() {
return {};
}
std::optional<PassthroughResponse> Avr8::invokePassthroughCommand(const PassthroughCommand& command) {
// AVR targets do not currently support any passthrough commands
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(
const std::vector<TargetPeripheralDescriptor>& portPeripheralDescriptors
) {
auto output = std::map<TargetPadId, GpioPadDescriptor>{};
for (const auto& peripheralDescriptor : portPeripheralDescriptors) {
if (peripheralDescriptor.registerGroupDescriptorsByKey.empty()) {
continue;
}
for (const auto& signalDescriptor : peripheralDescriptor.signalDescriptors) {
if (!signalDescriptor.index.has_value()) {
continue;
}
if (output.contains(signalDescriptor.padId)) {
continue;
}
const auto registerMask = static_cast<std::uint8_t>(0x01 << *(signalDescriptor.index));
/*
* All port peripherals should only have a single register group instance pointing to the port register
* group. This is enforced in the TDF validation script.
*
* The key of the register group instance varies across peripherals, which is why we use begin() as
* opposed to performing a key lookup.
*/
const auto& portRegisterGroup = peripheralDescriptor.registerGroupDescriptorsByKey.begin()->second;
// From a register layout perspective, there are two types of GPIO port modules on AVR8 targets.
if (portRegisterGroup.registerDescriptorsByKey.contains("outset")) {
output.emplace(
signalDescriptor.padId,
GpioPadDescriptor{
signalDescriptor.padKey,
registerMask,
portRegisterGroup.getRegisterDescriptor("dir"),
portRegisterGroup.getRegisterDescriptor("in"),
portRegisterGroup.getRegisterDescriptor("out")
}
);
continue;
}
/*
* The older GPIO port module is a little trickier, as it has a dedicated register group for each
* instance of the module (e.g. one for PORTA, another for PORTB, etc.).
*
* And the register keys are inconsistent ("ddra", "ddrb", etc.).
*/
auto ddrDescriptor = std::optional<std::reference_wrapper<const TargetRegisterDescriptor>>{};
auto inputDescriptor = std::optional<std::reference_wrapper<const TargetRegisterDescriptor>>{};
auto outputDescriptor = std::optional<std::reference_wrapper<const TargetRegisterDescriptor>>{};
for (const auto& [registerKey, registerDescriptor] : portRegisterGroup.registerDescriptorsByKey) {
if (registerKey.find("ddr") == 0) {
ddrDescriptor = std::cref(registerDescriptor);
continue;
}
if (registerKey.find("pin") == 0) {
inputDescriptor = std::cref(registerDescriptor);
continue;
}
if (registerKey.find("port") == 0) {
outputDescriptor = std::cref(registerDescriptor);
}
}
if (ddrDescriptor.has_value() && inputDescriptor.has_value() && outputDescriptor.has_value()) {
output.emplace(
signalDescriptor.padId,
GpioPadDescriptor{
signalDescriptor.padKey,
registerMask,
ddrDescriptor->get(),
inputDescriptor->get(),
outputDescriptor->get()
}
);
}
}
}
return output;
}
TargetMemoryBuffer Avr8::readRegister(const TargetRegisterDescriptor& descriptor) {
return this->readRegisters({&descriptor}).at(0).second;
}
void Avr8::writeRegister(const TargetRegisterDescriptor& descriptor, const TargetMemoryBuffer& value) {
this->writeRegisters({{descriptor, value}});
}
void Avr8::applyDebugInterfaceAccessRestrictions(
TargetRegisterGroupDescriptor& groupDescriptor,
const TargetAddressSpaceDescriptor& addressSpaceDescriptor
) {
for (auto& [registerKey, registerDescriptor] : groupDescriptor.registerDescriptorsByKey) {
this->avr8DebugInterface->applyAccessRestrictions(registerDescriptor, addressSpaceDescriptor);
}
for (auto& [subgroupKey, subgroupDescriptor] : groupDescriptor.subgroupDescriptorsByKey) {
this->applyDebugInterfaceAccessRestrictions(subgroupDescriptor, addressSpaceDescriptor);
}
}
BreakpointResources Avr8::getBreakpointResources() {
auto hardwareBreakpoints = std::uint32_t{0};
switch (this->targetConfig.physicalInterface) {
case TargetPhysicalInterface::JTAG: {
hardwareBreakpoints = this->family == Family::XMEGA ? 2 : 3;
break;
}
case TargetPhysicalInterface::PDI: {
hardwareBreakpoints = 2;
break;
}
case TargetPhysicalInterface::UPDI: {
hardwareBreakpoints = 1;
break;
}
default: {
break;
}
}
return BreakpointResources{
hardwareBreakpoints,
/*
* AVR targets have unlimited SW breakpoints, so we just use the program memory size divided by the
* breakpoint ("BREAK") opcode (byte) size, to determine the limit.
*/
this->programMemorySegmentDescriptor.size() / 2,
std::min(
static_cast<std::uint32_t>(this->targetConfig.reserveSteppingBreakpoint.value_or(true) ? 1 : 0),
hardwareBreakpoints
)
};
}
bool Avr8::isFuseEnabled(const TargetBitFieldDescriptor& bitFieldDescriptor, FuseValue value) const {
const auto programmedValue = static_cast<unsigned char>(
this->fuseEnableStrategy == FuseEnableStrategy::SET
? (0xFF & bitFieldDescriptor.mask)
: 0
);
return (value & bitFieldDescriptor.mask) == programmedValue;
}
FuseValue Avr8::setFuseEnabled(
const TargetBitFieldDescriptor& bitFieldDescriptor,
FuseValue value,
bool enabled
) const {
return static_cast<FuseValue>(
this->fuseEnableStrategy == FuseEnableStrategy::SET
? enabled
? (value | bitFieldDescriptor.mask)
: value & ~(bitFieldDescriptor.mask)
: enabled
? value & ~(bitFieldDescriptor.mask)
: (value | bitFieldDescriptor.mask)
);
}
void Avr8::updateDwenFuseBit(bool enable) {
if (this->avrIspInterface == nullptr) {
throw Exception{
"Debug tool or driver does not provide access to an ISP interface - please confirm that the "
"debug tool supports ISP and then report this issue via " + Services::PathService::homeDomainName()
+ "/report-issue"
};
}
if (!this->physicalInterfaces.contains(TargetPhysicalInterface::DEBUG_WIRE)) {
throw Exception{
"Target does not support debugWIRE physical interface - check target configuration or "
"report this issue via " + Services::PathService::homeDomainName() + "/report-issue"
};
}
const auto dwenFuseBitFieldPair = this->targetDescriptionFile.getFuseRegisterBitFieldDescriptorPair("dwen");
const auto& dwenRegisterDescriptor = dwenFuseBitFieldPair.first;
const auto& dwenBitFieldDescriptor = dwenFuseBitFieldPair.second;
const auto spienFuseBitFieldPair = this->targetDescriptionFile.getFuseRegisterBitFieldDescriptorPair("spien");
const auto& spienRegisterDescriptor = spienFuseBitFieldPair.first;
const auto& spienBitFieldDescriptor = spienFuseBitFieldPair.second;
assert(dwenRegisterDescriptor.size == 1);
assert(spienRegisterDescriptor.size == 1);
Logger::info("Initiating ISP interface");
this->avrIspInterface->activate();
/*
* AVR fuses are used to control certain functions within the AVR (including the debugWIRE interface). Care
* must be taken when updating these fuse bytes, as an incorrect value could render the AVR inaccessible to
* standard programmers.
*
* When updating the DWEN fuse, Bloom relies on data from the target description file (TDF). But there is no
* guarantee that this data is correct. For this reason, we perform additional checks in an attempt to reduce
* the likelihood of bricking the target:
*
* - Confirm target signature match - We read the AVR signature from the connected target and compare it to
* what we have in the TDF. The operation will be aborted if the signatures do not match.
*
* - SPIEN fuse bit check - we can be certain that the SPIEN fuse bit is set, because we couldn't have gotten
* this far (post ISP activation) if it wasn't. We use this axiom to verify the validity of the data in the
* TDF. If the SPIEN fuse bit appears to be cleared, we can be fairly certain that the data we have on the
* SPIEN fuse bit is incorrect. From this, we assume that the data for the DWEN fuse bit is also incorrect,
* and abort the operation.
*
* - Lock bits check - we read the lock bit byte from the target and confirm that all lock bits are cleared.
* If any lock bits are set, we abort the operation.
*
* - DWEN fuse bit check - if the DWEN fuse bit is already set to the desired value, then there is no need
* to update it. But we may be checking the wrong bit (if the TDF data is incorrect) - either way, we will
* abort the operation.
*
* The precautions described above may reduce the likelihood of Bloom bricking the connected target, but there
* is still a chance that all of the checks pass, and we still brick the device. Now would be a good time to
* remind the user of liabilities in regard to Bloom and its contributors.
*/
Logger::warning(
"Updating the DWEN fuse bit is a potentially dangerous operation. Bloom is provided \"AS IS\", "
"without warranty of any kind. You are using Bloom at your own risk. In no event shall the copyright "
"owner or contributors be liable for any damage caused as a result of using Bloom. For more details, "
"see the Bloom license at " + Services::PathService::homeDomainName() + "/license"
);
try {
Logger::info("Reading target signature via ISP");
const auto ispDeviceSignature = this->avrIspInterface->getDeviceId();
if (ispDeviceSignature != this->signature) {
throw Exception{
"AVR target signature mismatch - expected signature \"" + this->signature.toHex()
+ "\" but got \"" + ispDeviceSignature.toHex() + "\". Please check target configuration."
};
}
Logger::info("Target signature confirmed: " + ispDeviceSignature.toHex());
const auto dwenFuseByte = this->avrIspInterface->readFuse(dwenRegisterDescriptor);
const auto spienFuseByte = (spienRegisterDescriptor == dwenRegisterDescriptor)
? dwenFuseByte
: this->avrIspInterface->readFuse(spienRegisterDescriptor);
/*
* Keep in mind that, for AVR fuses and lock bits, a set bit (0b1) means the fuse/lock is cleared, and a
* cleared bit (0b0), means the fuse/lock is set.
*/
if (!this->isFuseEnabled(spienBitFieldDescriptor, spienFuseByte)) {
/*
* If we get here, something is very wrong. The SPIEN (SPI enable) fuse bit appears to be cleared, but
* this is not possible because we're connected to the target via the SPI (the ISP interface uses a
* physical SPI between the debug tool and the target).
*
* This could be (and likely is) caused by incorrect data for the SPIEN fuse bit, in the TDF (which was
* used to construct the spienFuseBitsDescriptor). And if the data for the SPIEN fuse bit is incorrect,
* then what's to say the data for the DWEN fuse bit (dwenFuseBitsDescriptor) is any better?
*
* We must assume the worst and abort the operation. Otherwise, we risk bricking the user's hardware.
*/
throw Exception{
"Invalid SPIEN fuse bit value - suspected inaccuracies in TDF data. Please report this to "
"Bloom developers as a matter of urgency, via " + Services::PathService::homeDomainName()
+ "/report-issue"
};
}
Logger::info("Current SPIEN fuse bit value confirmed");
if (this->isFuseEnabled(dwenBitFieldDescriptor, dwenFuseByte) == enable) {
/*
* The DWEN fuse appears to already be set to the desired value. This may be a result of incorrect data
* in the TDF, but we're not taking any chances.
*
* We don't throw an exception here, because we don't know if this is due to an error, or if the fuse
* bit is simply already set to the desired value.
*/
Logger::debug("DWEN fuse bit already set to desired value - aborting update operation");
this->avrIspInterface->deactivate();
return;
}
const auto lockBitByte = this->avrIspInterface->readLockBitByte();
if (lockBitByte != 0xFF) {
/*
* There is at least one lock bit that is set. Setting the DWEN fuse bit with the lock bits set may
* brick the device. We must abort.
*/
throw Exception{
"At least one lock bit has been set - updating the DWEN fuse bit could potentially brick "
"the target."
};
}
Logger::info("Cleared lock bits confirmed");
const auto newFuseValue = this->setFuseEnabled(dwenBitFieldDescriptor, dwenFuseByte, enable);
Logger::warning("Updating DWEN fuse bit");
this->avrIspInterface->programFuse(dwenRegisterDescriptor, newFuseValue);
Logger::debug("Verifying DWEN fuse bit");
if (this->avrIspInterface->readFuse(dwenRegisterDescriptor) != newFuseValue) {
throw Exception{"Failed to update DWEN fuse bit - post-update verification failed"};
}
Logger::info("DWEN fuse bit successfully updated");
this->avrIspInterface->deactivate();
} catch (const Exception& exception) {
this->avrIspInterface->deactivate();
throw exception;
}
}
void Avr8::updateOcdenFuseBit(bool enable) {
using Services::PathService;
using Services::StringService;
if (!this->physicalInterfaces.contains(TargetPhysicalInterface::JTAG)) {
throw Exception{
"Target does not support JTAG physical interface - check target configuration or "
"report this issue via " + PathService::homeDomainName() + "/report-issue"
};
}
const auto targetSignature = this->avr8DebugInterface->getDeviceId();
const auto tdSignature = this->targetDescriptionFile.getTargetSignature();
if (targetSignature != tdSignature) {
throw Exception{
"Failed to validate connected target - target signature mismatch.\nThe target signature"
" (\"" + targetSignature.toHex() + "\") does not match the AVR8 target description signature (\""
+ tdSignature.toHex() + "\"). This will likely be due to an incorrect target name in the "
+ "configuration file (bloom.yaml)."
};
}
const auto ocdenFuseBitFieldPair = this->targetDescriptionFile.getFuseRegisterBitFieldDescriptorPair("ocden");
const auto& ocdenRegisterDescriptor = ocdenFuseBitFieldPair.first;
const auto& ocdenBitFieldDescriptor = ocdenFuseBitFieldPair.second;
const auto jtagenFuseBitFieldPair = this->targetDescriptionFile.getFuseRegisterBitFieldDescriptorPair("jtagen");
const auto& jtagenRegisterDescriptor = jtagenFuseBitFieldPair.first;
const auto& jtagenBitFieldDescriptor = jtagenFuseBitFieldPair.second;
assert(ocdenRegisterDescriptor.size == 1);
assert(jtagenRegisterDescriptor.size == 1);
const auto ocdenFuseByteValue = this->avr8DebugInterface->readMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
ocdenRegisterDescriptor.startAddress,
1
).at(0);
const auto jtagenFuseByteValue = jtagenRegisterDescriptor == ocdenRegisterDescriptor
? ocdenFuseByteValue
: this->avr8DebugInterface->readMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
jtagenRegisterDescriptor.startAddress,
1
).at(0);
Logger::debug("OCDEN fuse byte value (before update): 0x" + StringService::toHex(ocdenFuseByteValue));
if (!this->isFuseEnabled(jtagenBitFieldDescriptor, jtagenFuseByteValue)) {
/*
* If we get here, something has gone wrong. The JTAGEN fuse should always be programmed by this point.
* We wouldn't have been able to activate the JTAG physical interface if the fuse wasn't programmed.
*
* This means the data we have on the JTAGEN fuse bit, from the TDF, is likely incorrect. And if that's
* the case, we cannot rely on the data for the OCDEN fuse bit being any better.
*/
throw Exception{
"Invalid JTAGEN fuse bit value - suspected inaccuracies in TDF data. Please report this to "
"Bloom developers as a matter of urgency, via " + PathService::homeDomainName() + "/report-issue"
};
}
if (this->isFuseEnabled(ocdenBitFieldDescriptor, ocdenFuseByteValue) == enable) {
Logger::debug("OCDEN fuse bit already set to desired value - aborting update operation");
return;
}
const auto newValue = this->setFuseEnabled(ocdenBitFieldDescriptor, ocdenFuseByteValue, enable);
Logger::debug("New OCDEN fuse byte value (to be written): 0x" + StringService::toHex(newValue));
Logger::warning("Updating OCDEN fuse bit");
this->avr8DebugInterface->writeMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
ocdenRegisterDescriptor.startAddress,
TargetMemoryBuffer({newValue})
);
Logger::debug("Verifying OCDEN fuse bit");
const auto postUpdateOcdenByteValue = this->avr8DebugInterface->readMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
ocdenRegisterDescriptor.startAddress,
1
).at(0);
if (postUpdateOcdenByteValue != newValue) {
throw Exception{"Failed to update OCDEN fuse bit - post-update verification failed"};
}
Logger::info("OCDEN fuse bit updated");
this->disableProgrammingMode();
}
bool Avr8::updateEesaveFuseBit(bool enable) {
using Services::StringService;
const auto eesaveFuseBitFieldPair = this->targetDescriptionFile.getFuseRegisterBitFieldDescriptorPair("eesave");
const auto& eesaveRegisterDescriptor = eesaveFuseBitFieldPair.first;
const auto& eesaveBitFieldDescriptor = eesaveFuseBitFieldPair.second;
assert(eesaveRegisterDescriptor.size == 1);
const auto eesaveFuseByteValue = this->avr8DebugInterface->readMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
eesaveRegisterDescriptor.startAddress,
1
).at(0);
Logger::debug("EESAVE fuse byte value (before update): 0x" + StringService::toHex(eesaveFuseByteValue));
if (this->isFuseEnabled(eesaveBitFieldDescriptor, eesaveFuseByteValue) == enable) {
Logger::debug("EESAVE fuse bit already set to desired value - aborting update operation");
return false;
}
const auto newValue = this->setFuseEnabled(eesaveBitFieldDescriptor, eesaveFuseByteValue, enable);
Logger::debug("New EESAVE fuse byte value (to be written): 0x" + StringService::toHex(newValue));
Logger::warning("Updating EESAVE fuse bit");
this->avr8DebugInterface->writeMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
eesaveRegisterDescriptor.startAddress,
TargetMemoryBuffer({newValue})
);
Logger::debug("Verifying EESAVE fuse bit");
const auto postUpdateEesaveByteValue = this->avr8DebugInterface->readMemory(
this->fuseAddressSpaceDescriptor,
this->fuseMemorySegmentDescriptor,
eesaveRegisterDescriptor.startAddress,
1
).at(0);
if (postUpdateEesaveByteValue != newValue) {
throw Exception{"Failed to update EESAVE fuse bit - post-update verification failed"};
}
Logger::info("EESAVE fuse bit updated");
return true;
}
}