#include "Avr8.hpp" #include #include #include #include #include #include #include "src/Logger/Logger.hpp" #include "src/Helpers/Paths.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "Exceptions/DebugWirePhysicalInterfaceError.hpp" #include "src/Targets/TargetRegister.hpp" #include "src/Targets/Microchip/AVR/Fuse.hpp" // Derived AVR8 targets #include "XMega/XMega.hpp" #include "Mega/Mega.hpp" #include "Tiny/Tiny.hpp" namespace Bloom::Targets::Microchip::Avr::Avr8Bit { using namespace Exceptions; void Avr8::preActivationConfigure(const TargetConfig& targetConfig) { Target::preActivationConfigure(targetConfig); auto physicalInterface = targetConfig.jsonObject.find("physicalInterface")->toString().toLower().toStdString(); auto availablePhysicalInterfaces = Avr8::getPhysicalInterfacesByName(); if (physicalInterface.empty() || !availablePhysicalInterfaces.contains(physicalInterface)) { throw InvalidConfig("Invalid or missing physical interface config parameter for AVR8 target."); } const auto selectedPhysicalInterface = availablePhysicalInterfaces.at(physicalInterface); if (selectedPhysicalInterface == PhysicalInterface::DEBUG_WIRE) { Logger::warning("AVR8 debugWire interface selected - the DWEN fuse will need to be enabled"); } this->physicalInterface = selectedPhysicalInterface; this->avr8DebugInterface->setPhysicalInterface(this->physicalInterface.value()); if (this->family.has_value()) { this->avr8DebugInterface->setFamily(this->family.value()); } else { if (this->physicalInterface == PhysicalInterface::JTAG) { throw InvalidConfig( "The JTAG physical interface cannot be used with an ambiguous target name" " - please specify the exact name of the target in your configuration file. " "See " + Paths::homeDomainName() + "/docs/supported-targets" ); } if (this->physicalInterface == PhysicalInterface::UPDI) { throw InvalidConfig( "The UPDI physical interface cannot be used with an ambiguous target name" " - please specify the exact name of the target in your configuration file. " "See " + Paths::homeDomainName() + "/docs/supported-targets" ); } } if (targetConfig.jsonObject.contains("updateDwenFuseBit")) { this->updateDwenFuseBit = targetConfig.jsonObject.value( "updateDwenFuseBit" ).toBool(); if (this->updateDwenFuseBit && this->avrIspInterface == nullptr && this->physicalInterface == PhysicalInterface::DEBUG_WIRE ) { Logger::warning( "The connected debug tool (or associated driver) does not provide any ISP interface. " "Bloom will be unable to update the DWEN fuse bit in the event of a debugWire activation failure." ); } } this->avr8DebugInterface->configure(targetConfig); if (this->avrIspInterface != nullptr) { this->avrIspInterface->configure(targetConfig); } } void Avr8::postActivationConfigure() { if (!this->targetDescriptionFile.has_value()) { this->loadTargetDescriptionFile(); this->initFromTargetDescriptionFile(); } /* * The signature obtained from the device should match what is in the target description file * * We don't use this->getId() here as that could return the ID that was extracted from the target description * file (which it would, if the user specified the exact target name in their project config - see * Avr8::getId() and TargetController::getSupportedTargets() for more). */ auto targetSignature = this->avr8DebugInterface->getDeviceId(); 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.json)." ); } } void Avr8::postPromotionConfigure() { if (!this->family.has_value()) { throw Exception("Failed to resolve AVR8 family"); } this->avr8DebugInterface->setFamily(this->family.value()); this->avr8DebugInterface->setTargetParameters(this->targetParameters.value()); } void Avr8::activate() { if (this->isActivated()) { return; } this->avr8DebugInterface->init(); if (this->targetDescriptionFile.has_value()) { this->avr8DebugInterface->setTargetParameters(this->targetParameters.value()); } try { this->avr8DebugInterface->activate(); } catch (const Exceptions::DebugWirePhysicalInterfaceError& debugWireException) { if (!this->updateDwenFuseBit) { 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 " + Paths::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->writeDwenFuseBit(true); } 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->activated = true; } void Avr8::deactivate() { try { this->avr8DebugInterface->deactivate(); this->activated = false; } catch (const Exception& exception) { Logger::error("Failed to deactivate AVR8 target - " + exception.getMessage()); } } std::unique_ptr Avr8::promote() { std::unique_ptr promoted = nullptr; if (this->family.has_value()) { // Promote generic AVR8 target to correct family. switch (this->family.value()) { case Family::XMEGA: { Logger::info("AVR8 target promoted to XMega target"); promoted = std::make_unique(*this); break; } case Family::MEGA: { Logger::info("AVR8 target promoted to megaAVR target"); promoted = std::make_unique(*this); break; } case Family::TINY: { Logger::info("AVR8 target promoted to tinyAVR target"); promoted = std::make_unique(*this); break; } default: { break; } } } return promoted; } TargetDescriptor Avr8Bit::Avr8::getDescriptor() { auto descriptor = TargetDescriptor(); descriptor.id = this->getHumanReadableId(); descriptor.name = this->getName(); descriptor.registerDescriptorsByType = this->targetRegisterDescriptorsByType; descriptor.memoryDescriptorsByType = this->targetMemoryDescriptorsByType; std::transform( this->targetVariantsById.begin(), this->targetVariantsById.end(), std::back_inserter(descriptor.variants), [] (auto& variantToIdPair) { return variantToIdPair.second; } ); return descriptor; } void Avr8::run() { this->avr8DebugInterface->run(); } void Avr8::stop() { this->avr8DebugInterface->stop(); } void Avr8::step() { this->avr8DebugInterface->step(); } void Avr8::reset() { this->avr8DebugInterface->reset(); } void Avr8::setBreakpoint(std::uint32_t address) { this->avr8DebugInterface->setBreakpoint(address); } void Avr8::removeBreakpoint(std::uint32_t address) { this->avr8DebugInterface->clearBreakpoint(address); } void Avr8::clearAllBreakpoints() { this->avr8DebugInterface->clearAllBreakpoints(); } void Avr8::writeRegisters(TargetRegisters registers) { for (auto registerIt = registers.begin(); registerIt != registers.end();) { if (registerIt->descriptor.type == TargetRegisterType::PROGRAM_COUNTER) { auto programCounterBytes = registerIt->value; if (programCounterBytes.size() < 4) { // All PC register values should be at least 4 bytes in size programCounterBytes.insert(programCounterBytes.begin(), 4 - programCounterBytes.size(), 0x00); } this->setProgramCounter(static_cast( programCounterBytes[0] << 24 | programCounterBytes[1] << 16 | programCounterBytes[2] << 8 | programCounterBytes[3] )); registerIt = registers.erase(registerIt); } else { registerIt++; } } if (!registers.empty()) { this->avr8DebugInterface->writeRegisters(registers); } } TargetRegisters Avr8::readRegisters(TargetRegisterDescriptors descriptors) { TargetRegisters registers; for (auto registerDescriptorIt = descriptors.begin(); registerDescriptorIt != descriptors.end();) { const auto& descriptor = *registerDescriptorIt; if (descriptor.type == TargetRegisterType::PROGRAM_COUNTER) { registers.push_back(this->getProgramCounterRegister()); registerDescriptorIt = descriptors.erase(registerDescriptorIt); } else { registerDescriptorIt++; } } if (!descriptors.empty()) { auto otherRegisters = this->avr8DebugInterface->readRegisters(descriptors); registers.insert(registers.end(), otherRegisters.begin(), otherRegisters.end()); } return registers; } TargetMemoryBuffer Avr8::readMemory( TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes, const std::set& excludedAddressRanges ) { return this->avr8DebugInterface->readMemory(memoryType, startAddress, bytes, excludedAddressRanges); } void Avr8::writeMemory(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer) { this->avr8DebugInterface->writeMemory(memoryType, startAddress, buffer); } TargetState Avr8::getState() { return this->avr8DebugInterface->getTargetState(); } std::uint32_t Avr8::getProgramCounter() { return this->avr8DebugInterface->getProgramCounter(); } TargetRegister Avr8::getProgramCounterRegister() { auto programCounter = this->getProgramCounter(); return TargetRegister(TargetRegisterDescriptor(TargetRegisterType::PROGRAM_COUNTER), { static_cast(programCounter >> 24), static_cast(programCounter >> 16), static_cast(programCounter >> 8), static_cast(programCounter), }); } void Avr8::setProgramCounter(std::uint32_t programCounter) { this->avr8DebugInterface->setProgramCounter(programCounter); } std::uint32_t Avr8::getStackPointer() { const auto stackPointerRegister = this->readRegisters( {this->targetRegisterDescriptorsByType.at(TargetRegisterType::STACK_POINTER)} ).front(); std::uint32_t stackPointer = 0; for (std::size_t i = 0; i < stackPointerRegister.size() && i < 4; i++) { stackPointer = (stackPointer << (8 * i)) | stackPointerRegister.value[i]; } return stackPointer; } std::map Avr8::getPinStates(int variantId) { if (!this->targetVariantsById.contains(variantId)) { throw Exception("Invalid target variant ID"); } std::map output; auto& variant = this->targetVariantsById.at(variantId); /* * To prevent the number of memory reads we perform here, we cache the data and map it by start address. * * This way, we only perform 3 memory reads for a target variant with 3 ports - one per port (instead of one * per pin). * * We may be able to make this more efficient by combining reads for ports with aligned memory addresses. This * will be considered when the need for it becomes apparent. */ std::map cachedMemoryByStartAddress; auto readMemoryBitset = [this, &cachedMemoryByStartAddress] (std::uint16_t startAddress) { if (!cachedMemoryByStartAddress.contains(startAddress)) { cachedMemoryByStartAddress.insert( std::pair( startAddress, this->readMemory(TargetMemoryType::RAM, startAddress, 1) ) ); } return std::bitset::digits>( cachedMemoryByStartAddress.at(startAddress).at(0) ); }; for (const auto& [pinNumber, pinDescriptor] : variant.pinDescriptorsByNumber) { if (this->padDescriptorsByName.contains(pinDescriptor.padName)) { auto& pad = this->padDescriptorsByName.at(pinDescriptor.padName); if (!pad.gpioPinNumber.has_value()) { continue; } auto pinState = TargetPinState(); if (pad.ddrSetAddress.has_value()) { auto dataDirectionRegisterValue = readMemoryBitset(pad.ddrSetAddress.value()); pinState.ioDirection = dataDirectionRegisterValue.test(pad.gpioPinNumber.value()) ? TargetPinState::IoDirection::OUTPUT : TargetPinState::IoDirection::INPUT; if (pinState.ioDirection == TargetPinState::IoDirection::OUTPUT && pad.gpioPortSetAddress.has_value() ) { auto portRegisterValue = readMemoryBitset(pad.gpioPortSetAddress.value()); pinState.ioState = portRegisterValue.test(pad.gpioPinNumber.value()) ? TargetPinState::IoState::HIGH : TargetPinState::IoState::LOW; } else if (pinState.ioDirection == TargetPinState::IoDirection::INPUT && pad.gpioPortInputAddress.has_value() ) { auto portInputRegisterValue = readMemoryBitset(pad.gpioPortInputAddress.value()); auto h = portInputRegisterValue.to_string(); pinState.ioState = portInputRegisterValue.test(pad.gpioPinNumber.value()) ? TargetPinState::IoState::HIGH : TargetPinState::IoState::LOW; } } output.insert(std::pair(pinNumber, pinState)); } } return output; } void Avr8::setPinState(const TargetPinDescriptor& pinDescriptor, const TargetPinState& state) { auto variantId = pinDescriptor.variantId; if (!this->targetVariantsById.contains(variantId)) { throw Exception("Invalid target variant ID"); } if (!this->padDescriptorsByName.contains(pinDescriptor.padName)) { throw Exception("Unknown pad"); } if (!state.ioDirection.has_value()) { throw Exception("Missing IO direction state"); } auto& variant = this->targetVariantsById.at(variantId); auto& padDescriptor = this->padDescriptorsByName.at(pinDescriptor.padName); auto ioState = state.ioState; if (state.ioDirection == TargetPinState::IoDirection::INPUT) { // When setting the direction to INPUT, we must always set the IO pinstate to LOW ioState = TargetPinState::IoState::LOW; } if (!padDescriptor.ddrSetAddress.has_value() || !padDescriptor.gpioPortSetAddress.has_value() || !padDescriptor.gpioPinNumber.has_value() ) { throw Exception("Inadequate pad descriptor"); } auto pinNumber = padDescriptor.gpioPinNumber.value(); auto ddrSetAddress = padDescriptor.ddrSetAddress.value(); auto ddrSetValue = this->readMemory(TargetMemoryType::RAM, ddrSetAddress, 1); if (ddrSetValue.empty()) { throw Exception("Failed to read DDSR value"); } auto ddrSetBitset = std::bitset::digits>(ddrSetValue.front()); if (ddrSetBitset.test(pinNumber) != (state.ioDirection == TargetPinState::IoDirection::OUTPUT)) { // DDR needs updating ddrSetBitset.set(pinNumber, (state.ioDirection == TargetPinState::IoDirection::OUTPUT)); this->writeMemory( TargetMemoryType::RAM, ddrSetAddress, {static_cast(ddrSetBitset.to_ulong())} ); } if (padDescriptor.ddrClearAddress.has_value() && padDescriptor.ddrClearAddress != ddrSetAddress) { // We also need to ensure the data direction clear register value is correct auto ddrClearAddress = padDescriptor.ddrClearAddress.value(); auto ddrClearValue = this->readMemory(TargetMemoryType::RAM, ddrClearAddress, 1); if (ddrClearValue.empty()) { throw Exception("Failed to read DDCR value"); } auto ddrClearBitset = std::bitset::digits>(ddrClearValue.front()); if (ddrClearBitset.test(pinNumber) == (state.ioDirection == TargetPinState::IoDirection::INPUT)) { ddrClearBitset.set(pinNumber, (state.ioDirection == TargetPinState::IoDirection::INPUT)); this->writeMemory( TargetMemoryType::RAM, ddrClearAddress, {static_cast(ddrClearBitset.to_ulong())} ); } } if (ioState.has_value()) { auto portSetAddress = padDescriptor.gpioPortSetAddress.value(); if (ioState == TargetPinState::IoState::HIGH || !padDescriptor.gpioPortClearAddress.has_value() || padDescriptor.gpioPortClearAddress == portSetAddress ) { if (padDescriptor.gpioPortClearAddress != portSetAddress) { /* * We don't need to read the SET register if the SET and CLEAR operations are performed via * different registers. * * Instead, we can just set the appropriate bit against the SET register. */ this->writeMemory( TargetMemoryType::RAM, portSetAddress, {static_cast(0x01 << pinNumber)} ); } else { auto portSetRegisterValue = this->readMemory( TargetMemoryType::RAM, portSetAddress, 1 ); if (portSetRegisterValue.empty()) { throw Exception("Failed to read PORT register value"); } auto portSetBitset = std::bitset::digits>( portSetRegisterValue.front() ); if (portSetBitset.test(pinNumber) != (ioState == TargetPinState::IoState::HIGH)) { // PORT set register needs updating portSetBitset.set(pinNumber, (ioState == TargetPinState::IoState::HIGH)); this->writeMemory( TargetMemoryType::RAM, portSetAddress, {static_cast(portSetBitset.to_ulong())} ); } } } /* * We only need to update the PORT clear register if the IO state was set to LOW, and the clear register is * not the same register as the set register. */ if (ioState == TargetPinState::IoState::LOW && padDescriptor.gpioPortClearAddress.has_value() && padDescriptor.gpioPortClearAddress != portSetAddress ) { // We also need to ensure the PORT clear register value is correct auto portClearAddress = padDescriptor.gpioPortClearAddress.value(); this->writeMemory( TargetMemoryType::RAM, portClearAddress, {static_cast(0x01 << pinNumber)} ); } } } void Avr8::loadTargetDescriptionFile() { this->targetDescriptionFile = TargetDescription::TargetDescriptionFile( this->getId(), (!this->name.empty()) ? std::optional(this->name) : std::nullopt ); } void Avr8::initFromTargetDescriptionFile() { assert(this->targetDescriptionFile.has_value()); this->name = this->targetDescriptionFile->getTargetName(); this->family = this->targetDescriptionFile->getFamily(); this->targetParameters = this->targetDescriptionFile->getTargetParameters(); this->padDescriptorsByName = this->targetDescriptionFile->getPadDescriptorsMappedByName(); this->targetVariantsById = this->targetDescriptionFile->getVariantsMappedById(); if (!this->targetParameters->stackPointerRegisterLowAddress.has_value()) { throw Exception( "Failed to load sufficient AVR8 target paramters - missting stack pointer start address" ); } if (!this->targetParameters->statusRegisterStartAddress.has_value()) { throw Exception( "Failed to load sufficient AVR8 target parameters - missting status register start address" ); } this->loadTargetRegisterDescriptors(); this->loadTargetMemoryDescriptors(); } void Avr8::loadTargetRegisterDescriptors() { this->targetRegisterDescriptorsByType = this->targetDescriptionFile->getRegisterDescriptorsMappedByType(); /* * All AVR8 targets possess 32 general purpose CPU registers. These are not described in the TDF, so we * construct the descriptors for them here. */ auto gpRegisterStartAddress = this->targetParameters->gpRegisterStartAddress.value_or(0); for (std::uint8_t i = 0; i <= 31; i++) { auto generalPurposeRegisterDescriptor = TargetRegisterDescriptor(); generalPurposeRegisterDescriptor.startAddress = gpRegisterStartAddress + i; generalPurposeRegisterDescriptor.size = 1; generalPurposeRegisterDescriptor.type = TargetRegisterType::GENERAL_PURPOSE_REGISTER; generalPurposeRegisterDescriptor.name = "r" + std::to_string(i); generalPurposeRegisterDescriptor.groupName = "general purpose cpu"; generalPurposeRegisterDescriptor.readable = true; generalPurposeRegisterDescriptor.writable = true; this->targetRegisterDescriptorsByType[generalPurposeRegisterDescriptor.type].insert( generalPurposeRegisterDescriptor ); } /* * The SP and SREG registers are described in the TDF, so we could just use the descriptors extracted from the * TDF. The problem with that is, sometimes the SP register consists of two bytes; an SPL and an SPH. These need * to be combined into one register descriptor. This is why we just use what we already have in * this->targetParameters. */ auto stackPointerRegisterDescriptor = TargetRegisterDescriptor(); stackPointerRegisterDescriptor.type = TargetRegisterType::STACK_POINTER; stackPointerRegisterDescriptor.startAddress = this->targetParameters->stackPointerRegisterLowAddress.value(); stackPointerRegisterDescriptor.size = this->targetParameters->stackPointerRegisterSize.value(); stackPointerRegisterDescriptor.name = "SP"; stackPointerRegisterDescriptor.groupName = "CPU"; stackPointerRegisterDescriptor.description = "Stack Pointer Register"; stackPointerRegisterDescriptor.readable = true; stackPointerRegisterDescriptor.writable = true; auto statusRegisterDescriptor = TargetRegisterDescriptor(); statusRegisterDescriptor.type = TargetRegisterType::STATUS_REGISTER; statusRegisterDescriptor.startAddress = this->targetParameters->statusRegisterStartAddress.value(); statusRegisterDescriptor.size = this->targetParameters->statusRegisterSize.value(); statusRegisterDescriptor.name = "SREG"; statusRegisterDescriptor.groupName = "CPU"; statusRegisterDescriptor.description = "Status Register"; statusRegisterDescriptor.readable = true; statusRegisterDescriptor.writable = true; auto programCounterRegisterDescriptor = TargetRegisterDescriptor(); programCounterRegisterDescriptor.type = TargetRegisterType::PROGRAM_COUNTER; programCounterRegisterDescriptor.size = 4; programCounterRegisterDescriptor.name = "PC"; programCounterRegisterDescriptor.groupName = "CPU"; programCounterRegisterDescriptor.description = "Program Counter"; programCounterRegisterDescriptor.readable = true; programCounterRegisterDescriptor.writable = true; this->targetRegisterDescriptorsByType[stackPointerRegisterDescriptor.type].insert( stackPointerRegisterDescriptor ); this->targetRegisterDescriptorsByType[statusRegisterDescriptor.type].insert( statusRegisterDescriptor ); this->targetRegisterDescriptorsByType[programCounterRegisterDescriptor.type].insert( programCounterRegisterDescriptor ); } void Avr8::loadTargetMemoryDescriptors() { const auto ramStartAddress = this->targetParameters->ramStartAddress.value(); this->targetMemoryDescriptorsByType.insert(std::pair( TargetMemoryType::RAM, TargetMemoryDescriptor( TargetMemoryType::RAM, TargetMemoryAddressRange( ramStartAddress, ramStartAddress + this->targetParameters->ramSize.value() - 1 ) ) )); if (this->targetParameters->eepromStartAddress.has_value() && this->targetParameters->eepromSize.has_value()) { const auto eepromStartAddress = this->targetParameters->eepromStartAddress.value(); this->targetMemoryDescriptorsByType.insert(std::pair( TargetMemoryType::EEPROM, TargetMemoryDescriptor( TargetMemoryType::EEPROM, TargetMemoryAddressRange( eepromStartAddress, eepromStartAddress + this->targetParameters->eepromSize.value() - 1 ) ) )); } } TargetSignature Avr8::getId() { if (!this->id.has_value()) { this->id = this->avr8DebugInterface->getDeviceId(); } return this->id.value(); } void Avr8::writeDwenFuseBit(bool setFuse) { 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 " + Paths::homeDomainName() + "/report-issue" ); } if (!this->targetDescriptionFile.has_value() || !this->id.has_value()) { throw Exception( "Insufficient target information for ISP interface - do not use the generic \"avr8\" " "target name in conjunction with the ISP interface. Please update your target configuration." ); } const auto& supportedPhysicalInterfaces = this->targetDescriptionFile->getSupportedDebugPhysicalInterfaces(); if (!supportedPhysicalInterfaces.contains(PhysicalInterface::DEBUG_WIRE)) { throw Exception( "Target does not support debugWire physical interface - check target configuration or " "report this issue via " + Paths::homeDomainName() + "/report-issue" ); } const auto dwenFuseBitsDescriptor = this->targetDescriptionFile->getDwenFuseBitsDescriptor(); const auto spienFuseBitsDescriptor = this->targetDescriptionFile->getSpienFuseBitsDescriptor(); if (!dwenFuseBitsDescriptor.has_value()) { throw Exception("Could not find DWEN bit field in TDF."); } if (!spienFuseBitsDescriptor.has_value()) { throw Exception("Could not find SPIEN bit field in TDF."); } Logger::debug("Extracting ISP parameters from TDF"); this->avrIspInterface->setIspParameters(this->targetDescriptionFile->getIspParameters()); Logger::info("Initiating ISP interface"); this->avrIspInterface->activate(); /* * It is crucial that we understand the potential consequences of this operation. * * 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. * * For example, consider the SPI enable (SPIEN) fuse bit. This fuse bit is used to enable/disable the SPI for * serial programming. If the SPIEN fuse bit is cleared, most programming tools will not be able to gain access * to the target via the SPI. This isn't too bad, if there is some other way for the programming tool to gain * access (such as the debugWire interface). But now consider the DWEN fuse bit (which is used to enable/disable * the debugWire interface). What if both the SPIEN *and* the DWEN fuse bits are cleared? Both interfaces will * be disabled. Effectively, the AVR will be bricked, and the only course for recovery would be to use * high-voltage programming. * * 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 regards 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 " + Paths::homeDomainName() + "/license" ); Logger::info("Reading target signature via ISP"); const auto ispDeviceId = this->avrIspInterface->getDeviceId(); if (ispDeviceId != this->id) { throw Exception( "AVR target signature mismatch - expected signature \"" + this->id->toHex() + "\" but got \"" + ispDeviceId.toHex() + "\". Please check target configuration." ); } Logger::info("Target signature confirmed: " + ispDeviceId.toHex()); const auto dwenFuseByte = this->avrIspInterface->readFuse(dwenFuseBitsDescriptor->fuseType).value; const auto spienFuseByte = (spienFuseBitsDescriptor->fuseType == dwenFuseBitsDescriptor->fuseType) ? dwenFuseByte : this->avrIspInterface->readFuse(spienFuseBitsDescriptor->fuseType).value; /* * Keep in mind that, for AVR fuses and lock bits, a set bit (1) means the fuse/lock is cleared, and a cleared * bit (0), means the fuse/lock is set. */ if ((spienFuseByte & spienFuseBitsDescriptor->bitMask) != 0) { /* * 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 " + Paths::homeDomainName() + "/report-issue" ); } Logger::info("Current SPIEN fuse bit value confirmed"); if (static_cast(dwenFuseByte & dwenFuseBitsDescriptor->bitMask) == !setFuse) { /* * 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. */ 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 will * 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 newFuse = Fuse( dwenFuseBitsDescriptor->fuseType, (setFuse) ? static_cast(dwenFuseByte & ~(dwenFuseBitsDescriptor->bitMask)) : static_cast(dwenFuseByte | dwenFuseBitsDescriptor->bitMask) ); Logger::warning("Programming DWEN fuse bit"); this->avrIspInterface->programFuse(newFuse); if (this->avrIspInterface->readFuse(dwenFuseBitsDescriptor->fuseType).value != newFuse.value) { Logger::error("Failed to program fuse bit - post-program value check failed"); } Logger::info("DWEN fuse bit successfully updated"); this->avrIspInterface->deactivate(); } }