diff --git a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp index beeb42a3..00955c9d 100644 --- a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp +++ b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp @@ -182,6 +182,13 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr * to access general purpose registers when other variants are in use. */ REGISTER_FILE = 0xB8, + + /** + * The FUSES memory type can be used to read and write AVR fuses in programming mode. + * + * Not available for the debugWire config variant. + */ + FUSES = 0xB2, }; enum class Avr8ResponseId: unsigned char diff --git a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp index 4488d103..6d208b1f 100644 --- a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp +++ b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp @@ -305,10 +305,6 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr } } - this->stop(); - this->clearAllBreakpoints(); - this->run(); - this->detach(); } @@ -664,6 +660,11 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr // EEPROM addresses should be in relative form, for XMEGA (PDI) targets startAddress -= this->targetParameters.eepromStartAddress.value(); } + break; + } + case TargetMemoryType::FUSES: { + avr8MemoryType = Avr8MemoryType::FUSES; + break; } default: { break; @@ -759,6 +760,11 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr break; } } + break; + } + case TargetMemoryType::FUSES: { + avr8MemoryType = Avr8MemoryType::FUSES; + break; } default: { break; @@ -881,14 +887,6 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr } this->programmingModeEnabled = false; - - if (this->configVariant == Avr8ConfigVariant::MEGAJTAG) { - this->deactivatePhysical(); - this->setTargetParameters(this->targetParameters); - this->targetAttached = false; - this->activate(); - this->stop(); - } } std::map> @@ -1687,6 +1685,12 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr TargetMemorySize bytes, const std::set& excludedAddresses ) { + if (type == Avr8MemoryType::FUSES) { + if (this->configVariant == Avr8ConfigVariant::DEBUG_WIRE) { + throw Exception("Cannot access AVR fuses via the debugWire interface"); + } + } + if (!excludedAddresses.empty() && (this->avoidMaskedMemoryRead || type != Avr8MemoryType::SRAM)) { /* * Driver-side masked memory read. @@ -1805,6 +1809,12 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr TargetMemoryAddress startAddress, const TargetMemoryBuffer& buffer ) { + if (type == Avr8MemoryType::FUSES) { + if (this->configVariant == Avr8ConfigVariant::DEBUG_WIRE) { + throw Exception("Cannot access AVR fuses via the debugWire interface"); + } + } + const auto bytes = static_cast(buffer.size()); if (this->alignmentRequired(type)) { diff --git a/src/DebugToolDrivers/TargetInterfaces/Microchip/AVR/AvrIspInterface.hpp b/src/DebugToolDrivers/TargetInterfaces/Microchip/AVR/AvrIspInterface.hpp index c6d2a69d..a7bf256c 100644 --- a/src/DebugToolDrivers/TargetInterfaces/Microchip/AVR/AvrIspInterface.hpp +++ b/src/DebugToolDrivers/TargetInterfaces/Microchip/AVR/AvrIspInterface.hpp @@ -13,6 +13,14 @@ namespace Bloom::DebugToolDrivers::TargetInterfaces::Microchip::Avr /** * Many AVRs can be programmed via an SPI interface. Some debug tools provide access to this interface via the AVR * In-System Programming (ISP) protocol. + * + * This interface class is incomplete - it only provides the ability to read the device ID and access AVR fuses and + * lockbit bytes (as that's all we need, for now). + * + * Currently, Bloom only uses the ISP interface for accessing fuses and lockbits on debugWire targets. We can't + * access fuses via the debugWire interface, so we have to use the ISP interface. + * + * @see Avr8::updateDwenFuseBit() for more. */ class AvrIspInterface { diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8.cpp b/src/Targets/Microchip/AVR/AVR8/Avr8.cpp index b6679522..807be90a 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8.cpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8.cpp @@ -8,6 +8,7 @@ #include "src/Logger/Logger.hpp" #include "src/Services/PathService.hpp" +#include "src/Services/StringService.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "Exceptions/DebugWirePhysicalInterfaceError.hpp" @@ -92,7 +93,8 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit } if ( - this->targetConfig->manageDwenFuseBit && this->avrIspInterface == nullptr + this->targetConfig->manageDwenFuseBit + && this->avrIspInterface == nullptr && this->targetConfig->physicalInterface == PhysicalInterface::DEBUG_WIRE ) { Logger::warning( @@ -101,10 +103,19 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit ); } - this->avr8DebugInterface->configure(this->targetConfig.value()); + if ( + this->targetConfig->manageOcdenFuseBit + && this->targetConfig->physicalInterface != PhysicalInterface::JTAG + ) { + Logger::warning( + "The 'manageOcdenFuseBit' parameter only applies to JTAG targets. It will be ignored in this session." + ); + } + + this->avr8DebugInterface->configure(*(this->targetConfig)); if (this->avrIspInterface != nullptr) { - this->avrIspInterface->configure(targetConfig); + this->avrIspInterface->configure(*(this->targetConfig)); } } @@ -211,13 +222,34 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit this->avr8DebugInterface->activate(); } + if ( + this->targetConfig->physicalInterface == PhysicalInterface::JTAG + && this->targetConfig->manageOcdenFuseBit + ) { + Logger::debug("Attempting OCDEN fuse bit management"); + this->updateOcdenFuseBit(true); + } + this->activated = true; this->avr8DebugInterface->reset(); } void Avr8::deactivate() { try { - this->avr8DebugInterface->deactivate(); + this->stop(); + this->clearAllBreakpoints(); + + if ( + this->targetConfig->physicalInterface == PhysicalInterface::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) { @@ -946,6 +978,110 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit } } + void Avr8::updateOcdenFuseBit(bool enable) { + using Services::PathService; + using Services::StringService; + + if (!this->targetDescriptionFile.has_value() || !this->id.has_value()) { + throw Exception( + "Insufficient target information for managing OCDEN fuse bit - do not use the generic \"avr8\" " + "target name in conjunction with the \"manageOcdenFuseBit\" function. Please update your target " + "configuration." + ); + } + + if (!this->supportedPhysicalInterfaces.contains(PhysicalInterface::JTAG)) { + throw Exception( + "Target does not support JTAG physical interface - check target configuration or " + "report this issue via " + PathService::homeDomainName() + "/report-issue" + ); + } + + const auto ocdenFuseBitsDescriptor = this->targetDescriptionFile->getOcdenFuseBitsDescriptor(); + const auto jtagenFuseBitsDescriptor = this->targetDescriptionFile->getJtagenFuseBitsDescriptor(); + + if (!ocdenFuseBitsDescriptor.has_value()) { + throw Exception("Could not find OCDEN bit field in TDF."); + } + + if (!jtagenFuseBitsDescriptor.has_value()) { + throw Exception("Could not find JTAGEN bit field in TDF."); + } + + try { + this->enableProgrammingMode(); + + const auto ocdenFuseByteValue = this->avr8DebugInterface->readMemory( + TargetMemoryType::FUSES, + ocdenFuseBitsDescriptor->byteAddress, + 1 + ).at(0); + const auto jtagenFuseByteValue = jtagenFuseBitsDescriptor->byteAddress == ocdenFuseBitsDescriptor->byteAddress + ? ocdenFuseByteValue + : this->avr8DebugInterface->readMemory( + TargetMemoryType::FUSES, + jtagenFuseBitsDescriptor->byteAddress, + 1 + ).at(0) + ; + + Logger::debug("OCDEN fuse byte value (before update): 0x" + StringService::toHex(ocdenFuseByteValue)); + + if ((jtagenFuseByteValue & jtagenFuseBitsDescriptor->bitMask) != 0) { + /* + * 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 (!static_cast(ocdenFuseByteValue & ocdenFuseBitsDescriptor->bitMask) == enable) { + Logger::debug("OCDEN fuse bit already set to desired value - aborting update operation"); + + this->disableProgrammingMode(); + return; + } + + const auto newValue = (enable) + ? static_cast(ocdenFuseByteValue & ~(ocdenFuseBitsDescriptor->bitMask)) + : static_cast(ocdenFuseByteValue | ocdenFuseBitsDescriptor->bitMask); + + Logger::debug("New OCDEN fuse byte value (to be written): 0x" + StringService::toHex(newValue)); + + Logger::warning("Updating OCDEN fuse bit"); + this->avr8DebugInterface->writeMemory( + TargetMemoryType::FUSES, + ocdenFuseBitsDescriptor->byteAddress, + {newValue} + ); + + Logger::debug("Verifying OCDEN fuse bit"); + const auto postUpdateOcdenByteValue = this->avr8DebugInterface->readMemory( + TargetMemoryType::FUSES, + ocdenFuseBitsDescriptor->byteAddress, + 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(); + + } catch (const Exception& exception) { + this->disableProgrammingMode(); + throw exception; + } + } + ProgramMemorySection Avr8::getProgramMemorySectionFromAddress(std::uint32_t address) { return this->targetParameters->bootSectionStartAddress.has_value() && address >= this->targetParameters->bootSectionStartAddress.value() diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8.hpp b/src/Targets/Microchip/AVR/AVR8/Avr8.hpp index 8b273228..783f0db1 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8.hpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8.hpp @@ -183,6 +183,14 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit */ void updateDwenFuseBit(bool enable); + /** + * Updates the On-chip debug enable (OCDEN) fuse bit on the AVR target. + * + * @param enable + * True to enable the fuse, false to disable it. + */ + void updateOcdenFuseBit(bool enable); + /** * Resolves the program memory section from a program memory address. * diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.cpp b/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.cpp index 841c1ea2..64bf9ff4 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.cpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.cpp @@ -53,5 +53,9 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit if (targetNode["targetPowerCycleDelay"]) { this->targetPowerCycleDelay = std::chrono::milliseconds(targetNode["targetPowerCycleDelay"].as()); } + + if (targetNode["manageOcdenFuseBit"]) { + this->manageOcdenFuseBit = targetNode["manageOcdenFuseBit"].as(); + } } } diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.hpp b/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.hpp index 913140aa..ae47df67 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.hpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8TargetConfig.hpp @@ -67,6 +67,15 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit */ std::chrono::milliseconds targetPowerCycleDelay = std::chrono::milliseconds(250); + /** + * The manageOcdenFuseBit flag determines if Bloom should manage the OCDEN fuse but on JTAG-enabled AVR + * targets. + * + * This parameter is optional, and the function is disabled by default. Users must explicitly enable it in + * their target configuration. + */ + bool manageOcdenFuseBit = false; + explicit Avr8TargetConfig(const TargetConfig& targetConfig); private: diff --git a/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.cpp b/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.cpp index 78d4ecde..36f6435c 100644 --- a/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.cpp +++ b/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.cpp @@ -394,6 +394,14 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription return this->getFuseBitsDescriptorByName("spien"); } + std::optional TargetDescriptionFile::getOcdenFuseBitsDescriptor() const { + return this->getFuseBitsDescriptorByName("ocden"); + } + + std::optional TargetDescriptionFile::getJtagenFuseBitsDescriptor() const { + return this->getFuseBitsDescriptorByName("jtagen"); + } + void TargetDescriptionFile::loadSupportedPhysicalInterfaces() { auto interfaceNamesToInterfaces = std::map({ {"updi", PhysicalInterface::UPDI}, @@ -665,6 +673,24 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription std::optional TargetDescriptionFile::getFuseBitsDescriptorByName( const std::string& fuseBitName ) const { + const auto& peripheralModules = this->getPeripheralModulesMappedByName(); + std::uint32_t fuseAddressOffset = 0; + + const auto fusePeripheralModuleIt = peripheralModules.find("fuse"); + if (fusePeripheralModuleIt != peripheralModules.end()) { + const auto& fusePeripheralModule = fusePeripheralModuleIt->second; + + const auto fuseInstanceIt = fusePeripheralModule.instancesMappedByName.find("fuse"); + if (fuseInstanceIt != fusePeripheralModule.instancesMappedByName.end()) { + const auto& fuseInstance = fuseInstanceIt->second; + + const auto fuseRegisterGroupIt = fuseInstance.registerGroupsMappedByName.find("fuse"); + if (fuseRegisterGroupIt != fuseInstance.registerGroupsMappedByName.end()) { + fuseAddressOffset = fuseRegisterGroupIt->second.offset.value_or(0); + } + } + } + const auto fuseModuleIt = this->modulesMappedByName.find("fuse"); if (fuseModuleIt == this->modulesMappedByName.end()) { @@ -687,7 +713,8 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription {"extended", FuseType::EXTENDED}, }); - for (const auto&[fuseTypeName, fuse] : fuseRegisterGroup.registersMappedByName) { + + for (const auto& [fuseTypeName, fuse] : fuseRegisterGroup.registersMappedByName) { const auto fuseTypeIt = fuseTypesByName.find(fuseTypeName); if (fuseTypeIt == fuseTypesByName.end()) { // Unknown fuse type name @@ -698,6 +725,7 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription if (fuseBitFieldIt != fuse.bitFieldsMappedByName.end()) { return FuseBitsDescriptor( + fuseAddressOffset + fuse.offset, fuseTypeIt->second, fuseBitFieldIt->second.mask ); diff --git a/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.hpp b/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.hpp index 18b07300..78cc0dc2 100644 --- a/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.hpp +++ b/src/Targets/Microchip/AVR/AVR8/TargetDescription/TargetDescriptionFile.hpp @@ -102,6 +102,24 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription */ [[nodiscard]] std::optional getSpienFuseBitsDescriptor() const; + /** + * Constructs a FuseBitDescriptor for the OCD enable (OCDEN) fuse bit, with information extracted from + * the TDF. + * + * @return + * std::nullopt if the OCDEN bit field could not be found in the TDF. + */ + [[nodiscard]] std::optional getOcdenFuseBitsDescriptor() const; + + /** + * Constructs a FuseBitDescriptor for the JTAG enable (JTAGEN) fuse bit, with information extracted from + * the TDF. + * + * @return + * std::nullopt if the JTAGEN bit field could not be found in the TDF. + */ + [[nodiscard]] std::optional getJtagenFuseBitsDescriptor() const; + /** * Returns a set of all supported physical interfaces for debugging. * diff --git a/src/Targets/Microchip/AVR/Fuse.hpp b/src/Targets/Microchip/AVR/Fuse.hpp index dddda312..4afc5f7d 100644 --- a/src/Targets/Microchip/AVR/Fuse.hpp +++ b/src/Targets/Microchip/AVR/Fuse.hpp @@ -2,6 +2,8 @@ #include +#include "src/Targets/TargetMemory.hpp" + namespace Bloom::Targets::Microchip::Avr { enum class FuseType: std::uint8_t @@ -24,6 +26,8 @@ namespace Bloom::Targets::Microchip::Avr struct FuseBitsDescriptor { + TargetMemoryAddress byteAddress; + /** * The type of the fuse byte in which the fuse bits resides. */ @@ -34,8 +38,9 @@ namespace Bloom::Targets::Microchip::Avr */ std::uint8_t bitMask; - FuseBitsDescriptor(FuseType fuseType, std::uint8_t bitMask) - : fuseType(fuseType) + FuseBitsDescriptor(TargetMemoryAddress byteAddress, FuseType fuseType, std::uint8_t bitMask) + : byteAddress(byteAddress) + , fuseType(fuseType) , bitMask(bitMask) {} }; diff --git a/src/Targets/TargetMemory.hpp b/src/Targets/TargetMemory.hpp index ac1a23bb..31c8326e 100644 --- a/src/Targets/TargetMemory.hpp +++ b/src/Targets/TargetMemory.hpp @@ -23,6 +23,7 @@ namespace Bloom::Targets FLASH, RAM, EEPROM, + FUSES, OTHER, };