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 0e7b230b..9c43fddb 100644 --- a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp +++ b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp @@ -129,7 +129,16 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr EEPROM = 0x22, /** - * The FLASH_PAGE memory type can be used to read &write full flash pages on the target. + * The EEPROM_ATOMIC memory type can be used to write to EEPROM memory with automatic pag erasing. + * + * It's only available for XMEGA and UPDI config variants. + * + * Only one EEPROM page can be written at a time. + */ + EEPROM_ATOMIC = 0xC4, + + /** + * The FLASH_PAGE memory type can be used to read and write full flash pages on the target. * * Only available with the JTAG and debugWire config variants. * 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 d2dd1ea5..71f899f2 100644 --- a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp +++ b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp @@ -734,7 +734,10 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr break; } case TargetMemoryType::EEPROM: { - avr8MemoryType = Avr8MemoryType::EEPROM; + avr8MemoryType = + this->configVariant == Avr8ConfigVariant::XMEGA || this->configVariant == Avr8ConfigVariant::UPDI + ? Avr8MemoryType::EEPROM_ATOMIC + : Avr8MemoryType::EEPROM; } default: { break; @@ -1239,6 +1242,10 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr throw DeviceInitializationFailure("Missing required parameter: SIGNATURE BASE ADDRESS"); } + if (!this->targetParameters.eepromPageSize.has_value()) { + throw DeviceInitializationFailure("Missing required parameter: UPDI_EEPROM_PAGE_SIZE"); + } + if (this->targetParameters.programMemoryUpdiStartAddress.has_value()) { /* * The program memory base address field for UPDI sessions (DEVICE_UPDI_PROGMEM_BASE_ADDR) seems to be @@ -1537,6 +1544,30 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr return bytes; } + std::optional EdbgAvr8Interface::maximumMemoryAccessSize(Avr8MemoryType memoryType) { + if ( + memoryType == Avr8MemoryType::FLASH_PAGE + || memoryType == Avr8MemoryType::APPL_FLASH + || memoryType == Avr8MemoryType::BOOT_FLASH + || (memoryType == Avr8MemoryType::SPM && this->configVariant == Avr8ConfigVariant::MEGAJTAG) + ) { + // These flash memory types require single page access. + return this->targetParameters.flashPageSize.value(); + } + + if (memoryType == Avr8MemoryType::EEPROM_ATOMIC) { + // This EEPROM memory type requires single page access. + return this->targetParameters.eepromPageSize.value(); + } + + if (this->maximumMemoryAccessSizePerRequest.has_value()) { + // There is a memory access size limit for this entire EdbgAvr8Interface instance + return this->maximumMemoryAccessSizePerRequest; + } + + return std::nullopt; + } + TargetMemoryBuffer EdbgAvr8Interface::readMemory( Avr8MemoryType type, TargetMemoryAddress startAddress, @@ -1611,48 +1642,15 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr } } - if ( - type == Avr8MemoryType::FLASH_PAGE - || type == Avr8MemoryType::APPL_FLASH - || type == Avr8MemoryType::BOOT_FLASH - || (type == Avr8MemoryType::SPM && this->configVariant == Avr8ConfigVariant::MEGAJTAG) - ) { - // With the FLASH_PAGE, APPL_FLASH, BOOT_FLASH and SPM memory types, we can only read one page at a time. - const auto pageSize = this->targetParameters.flashPageSize.value(); - - if (bytes > pageSize) { - // bytes should always be a multiple of pageSize (given the code above) - assert(bytes % pageSize == 0); - int pagesRequired = static_cast(bytes / pageSize); - auto memoryBuffer = Targets::TargetMemoryBuffer(); - - for (auto i = 0; i < pagesRequired; i++) { - auto pageBuffer = this->readMemory( - type, - startAddress + static_cast(pageSize * i), - pageSize - ); - std::move(pageBuffer.begin(), pageBuffer.end(), std::back_inserter(memoryBuffer)); - } - - return memoryBuffer; - } - } - - /* - * Enforce a maximum memory access request size. - * - * See the comment for EdbgAvr8Interface::setMaximumMemoryAccessSizePerRequest() for more on this. - */ - if (this->maximumMemoryAccessSizePerRequest.has_value() && bytes > this->maximumMemoryAccessSizePerRequest) { - auto maximumRequestSize = this->maximumMemoryAccessSizePerRequest.value(); - auto totalReadsRequired = std::ceil(static_cast(bytes) / static_cast(maximumRequestSize)); + const auto maximumReadSize = this->maximumMemoryAccessSize(type); + if (maximumReadSize.has_value() && bytes > *maximumReadSize) { auto output = Targets::TargetMemoryBuffer(); output.reserve(bytes); - for (float i = 1; i <= totalReadsRequired; i++) { - const auto bytesToRead = static_cast( - (bytes - output.size()) > maximumRequestSize ? maximumRequestSize : bytes - output.size() + while (output.size() < bytes) { + const auto bytesToRead = std::min( + static_cast(bytes - output.size()), + *maximumReadSize ); auto data = this->readMemory( @@ -1661,7 +1659,7 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr bytesToRead, excludedAddresses ); - output.insert(output.end(), data.begin(), data.end()); + std::move(data.begin(), data.end(), std::back_inserter(output)); } return output; @@ -1754,33 +1752,29 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr } } - if ( - type == Avr8MemoryType::FLASH_PAGE - || type == Avr8MemoryType::APPL_FLASH - || type == Avr8MemoryType::BOOT_FLASH - ) { - // With the FLASH_PAGE, APPL_FLASH and BOOT_FLASH memory types, we can only write one page at a time. - const auto pageSize = this->targetParameters.flashPageSize.value(); + const auto maximumWriteSize = this->maximumMemoryAccessSize(type); + if (maximumWriteSize.has_value() && buffer.size() > *maximumWriteSize) { + auto bytesWritten = TargetMemorySize(0); - if (bytes > pageSize) { - assert(bytes % pageSize == 0); - int pagesRequired = static_cast(bytes / pageSize); + while (bytesWritten < buffer.size()) { + const auto chunkSize = std::min( + static_cast(buffer.size() - bytesWritten), + *maximumWriteSize + ); - for (auto i = 0; i < pagesRequired; i++) { - const auto offset = static_cast(pageSize * i); - auto pageBuffer = TargetMemoryBuffer(); - pageBuffer.reserve(pageSize); - std::move( - buffer.begin() + offset, - buffer.begin() + offset + pageSize, - std::back_inserter(pageBuffer) - ); + this->writeMemory( + type, + startAddress + bytesWritten, + TargetMemoryBuffer( + buffer.begin() + bytesWritten, + buffer.begin() + bytesWritten + chunkSize + ) + ); - this->writeMemory(type, startAddress + offset, pageBuffer); - } - - return; + bytesWritten += chunkSize; } + + return; } const auto responseFrame = this->edbgInterface->sendAvrCommandFrameAndWaitForResponseFrame( diff --git a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp index eb529089..57b610cc 100644 --- a/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp +++ b/src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp @@ -505,6 +505,16 @@ namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr */ Targets::TargetMemorySize alignMemoryBytes(Avr8MemoryType memoryType, Targets::TargetMemorySize bytes); + /** + * Checks if a maximum memory access size is imposed for a given Avr8MemoryType. + * + * @param memoryType + * The imposed maximum size, or std::nullopt if a maximum isn't required. + * + * @return + */ + std::optional maximumMemoryAccessSize(Avr8MemoryType memoryType); + /** * Reads memory on the target. * diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8.cpp b/src/Targets/Microchip/AVR/AVR8/Avr8.cpp index 7c1072bc..92d3ec7b 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8.cpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8.cpp @@ -367,51 +367,8 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit } void Avr8::writeMemory(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer) { - if ( - memoryType == TargetMemoryType::FLASH && this->programmingSession.has_value() - && this->targetConfig->physicalInterface != PhysicalInterface::DEBUG_WIRE - ) { - if (this->targetConfig->physicalInterface == PhysicalInterface::PDI) { - const auto startSection = this->getProgramMemorySectionFromAddress(startAddress); - const auto endSection = this->getProgramMemorySectionFromAddress( - static_cast(startAddress + buffer.size() - 1) - ); - - if (startSection != endSection) { - throw Exception( - "Requested program memory write spans more than one section (APPLICATION and BOOT) - aborting" - ); - } - - if ( - !this->programmingSession->applicationSectionErased - && ( - startSection == ProgramMemorySection::APPLICATION - || endSection == ProgramMemorySection::APPLICATION - ) - ) { - Logger::warning("Erasing program memory APPLICATION section, in preparation for programming"); - this->avr8DebugInterface->eraseProgramMemory(ProgramMemorySection::APPLICATION); - this->programmingSession->applicationSectionErased = true; - } - - if ( - !this->programmingSession->bootSectionErased - && ( - startSection == ProgramMemorySection::BOOT - || endSection == ProgramMemorySection::BOOT - ) - ) { - Logger::warning("Erasing program memory BOOT section, in preparation for programming"); - this->avr8DebugInterface->eraseProgramMemory(ProgramMemorySection::BOOT); - this->programmingSession->bootSectionErased = true; - } - - } else if (!this->programmingSession->chipErased) { - Logger::warning("Erasing entire chip, in preparation for programming"); - this->avr8DebugInterface->eraseProgramMemory(); - this->programmingSession->chipErased = true; - } + if (memoryType == TargetMemoryType::FLASH) { + return this->writeFlashMemory(startAddress, buffer); } this->avr8DebugInterface->writeMemory(memoryType, startAddress, buffer); @@ -774,6 +731,73 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit return this->id.value(); } + void Avr8::writeFlashMemory(TargetMemoryAddress startAddress, const TargetMemoryBuffer& buffer) { + if (!this->programmingSession.has_value()) { + throw Exception("Attempted FLASH memory write with no active programming session."); + } + + if (this->targetConfig->physicalInterface == PhysicalInterface::PDI) { + /* + * For PDI targets, we can erase specific sections (APPLICATION and BOOTLOADER sections) of program memory. + * + * We'll only erase the section if we intend to write to it. + */ + const auto startSection = this->getProgramMemorySectionFromAddress(startAddress); + const auto endSection = this->getProgramMemorySectionFromAddress( + static_cast(startAddress + buffer.size() - 1) + ); + + if (startSection != endSection) { + /* + * TODO: + * Get rid of this. Was placed here because I didn't have enough time to implement and test the + * writing to multiple sections in a single instance. + */ + throw Exception( + "Requested program memory write spans more than one section (APPLICATION and BOOT) - aborting" + ); + } + + if ( + !this->programmingSession->applicationSectionErased + && ( + startSection == ProgramMemorySection::APPLICATION + || endSection == ProgramMemorySection::APPLICATION + ) + ) { + Logger::warning("Erasing program memory APPLICATION section, in preparation for programming"); + this->avr8DebugInterface->eraseProgramMemory(ProgramMemorySection::APPLICATION); + this->programmingSession->applicationSectionErased = true; + } + + if ( + !this->programmingSession->bootSectionErased + && ( + startSection == ProgramMemorySection::BOOT + || endSection == ProgramMemorySection::BOOT + ) + ) { + Logger::warning("Erasing program memory BOOT section, in preparation for programming"); + this->avr8DebugInterface->eraseProgramMemory(ProgramMemorySection::BOOT); + this->programmingSession->bootSectionErased = true; + } + + this->programmingSession->chipErased = true; + } + + // debugWire targets do not need to be erased - this is done automatically when writing to FLASH. + if ( + this->targetConfig->physicalInterface != PhysicalInterface::DEBUG_WIRE + && !this->programmingSession->chipErased + ) { + Logger::warning("Erasing entire chip, in preparation for programming"); + this->avr8DebugInterface->eraseProgramMemory(); + this->programmingSession->chipErased = true; + } + + return this->avr8DebugInterface->writeMemory(TargetMemoryType::FLASH, startAddress, buffer); + } + void Avr8::updateDwenFuseBit(bool enable) { if (this->avrIspInterface == nullptr) { throw Exception( diff --git a/src/Targets/Microchip/AVR/AVR8/Avr8.hpp b/src/Targets/Microchip/AVR/AVR8/Avr8.hpp index ce3f7021..71849b5a 100644 --- a/src/Targets/Microchip/AVR/AVR8/Avr8.hpp +++ b/src/Targets/Microchip/AVR/AVR8/Avr8.hpp @@ -175,6 +175,14 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit */ TargetSignature getId() override; + /** + * Writes to FLASH memory (with any necessary erasing). + * + * @param startAddress + * @param buffer + */ + void writeFlashMemory(TargetMemoryAddress startAddress, const TargetMemoryBuffer& buffer); + /** * Updates the debugWire enable (DWEN) fuse bit on the AVR target. *