OCDEN fuse bit management

This commit is contained in:
Nav
2023-05-07 16:49:45 +01:00
parent e2f202d5c9
commit 6ae1ef1be2
11 changed files with 253 additions and 19 deletions

View File

@@ -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

View File

@@ -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<Family, std::map<PhysicalInterface, Avr8ConfigVariant>>
@@ -1687,6 +1685,12 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
TargetMemorySize bytes,
const std::set<TargetMemoryAddress>& 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<TargetMemorySize>(buffer.size());
if (this->alignmentRequired(type)) {

View File

@@ -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
{

View File

@@ -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<bool>(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<unsigned char>(ocdenFuseByteValue & ~(ocdenFuseBitsDescriptor->bitMask))
: static_cast<unsigned char>(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()

View File

@@ -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.
*

View File

@@ -53,5 +53,9 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit
if (targetNode["targetPowerCycleDelay"]) {
this->targetPowerCycleDelay = std::chrono::milliseconds(targetNode["targetPowerCycleDelay"].as<int>());
}
if (targetNode["manageOcdenFuseBit"]) {
this->manageOcdenFuseBit = targetNode["manageOcdenFuseBit"].as<bool>();
}
}
}

View File

@@ -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:

View File

@@ -394,6 +394,14 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription
return this->getFuseBitsDescriptorByName("spien");
}
std::optional<FuseBitsDescriptor> TargetDescriptionFile::getOcdenFuseBitsDescriptor() const {
return this->getFuseBitsDescriptorByName("ocden");
}
std::optional<FuseBitsDescriptor> TargetDescriptionFile::getJtagenFuseBitsDescriptor() const {
return this->getFuseBitsDescriptorByName("jtagen");
}
void TargetDescriptionFile::loadSupportedPhysicalInterfaces() {
auto interfaceNamesToInterfaces = std::map<std::string, PhysicalInterface>({
{"updi", PhysicalInterface::UPDI},
@@ -665,6 +673,24 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription
std::optional<FuseBitsDescriptor> 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
);

View File

@@ -102,6 +102,24 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit::TargetDescription
*/
[[nodiscard]] std::optional<FuseBitsDescriptor> 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<FuseBitsDescriptor> 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<FuseBitsDescriptor> getJtagenFuseBitsDescriptor() const;
/**
* Returns a set of all supported physical interfaces for debugging.
*

View File

@@ -2,6 +2,8 @@
#include <cstdint>
#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)
{}
};

View File

@@ -23,6 +23,7 @@ namespace Bloom::Targets
FLASH,
RAM,
EEPROM,
FUSES,
OTHER,
};