diff --git a/hal_st/stm32fxxx/CMakeLists.txt b/hal_st/stm32fxxx/CMakeLists.txt index 75eb65dd..b61a7c53 100644 --- a/hal_st/stm32fxxx/CMakeLists.txt +++ b/hal_st/stm32fxxx/CMakeLists.txt @@ -68,6 +68,8 @@ target_sources(hal_st.stm32fxxx PRIVATE $<$:FlashInternalStmBle.hpp> GpioStm.cpp GpioStm.hpp + $<$:HighCycleAreaOrOtpIrqHandler.cpp> + $<$:HighCycleAreaOrOtpIrqHandler.hpp> $<$>:I2cStm.cpp> $<$>:I2cStm.hpp> QuadSpiStm.cpp @@ -80,6 +82,8 @@ target_sources(hal_st.stm32fxxx PRIVATE LpTimerStm.hpp $<$>:LpTimerPwmStm.cpp> $<$>:LpTimerPwmStm.hpp> + $<$:OneTimeProgrammableFlashStm.cpp> + $<$:OneTimeProgrammableFlashStm.hpp> RandomDataGeneratorStm.cpp RandomDataGeneratorStm.hpp ResetStm.cpp diff --git a/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.cpp b/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.cpp index 17f356dd..2ed97739 100644 --- a/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.cpp +++ b/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.cpp @@ -1,10 +1,10 @@ -#include DEVICE_HEADER #include "hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.hpp" #include "hal_st/stm32fxxx/FlashInternalStmDetail.hpp" #include "infra/event/EventDispatcher.hpp" #include "infra/util/ByteRange.hpp" #include "infra/util/MemoryRange.hpp" #include +#include DEVICE_HEADER namespace { @@ -38,10 +38,10 @@ namespace } } - void Copy(const uint16_t* first, const uint16_t* last, uint16_t* result) + void Copy(const uint16_t* begin, const uint16_t* end, uint16_t* destination) { - for (; first != last; ++result, ++first) - *result = *first; + for (; begin != end; ++destination, ++begin) + *destination = *begin; } } @@ -139,19 +139,6 @@ namespace hal FlashInternalHighCycleAreaStm::WithIrqHandler::WithIrqHandler(uint32_t bank) : FlashInternalHighCycleAreaStm(bank) - , nmi{ - IRQn_Type::NonMaskableInt_IRQn, [] - { - // From RM0481, Section 7.3.4 FLASH read operations: - // If the application reads an OTP data or flash high-cycle data not previously written, a - // double ECC error is reported and only a word full of set bits is returned (see - // Section 7.3.9 for details). The read data (in 16 bits) is stored in FLASH_ECCDR - // register, so that the user can identify if the double ECC error is due to a virgin data or a - // real ECC error. - really_assert(__HAL_FLASH_GET_FLAG(FLASH_FLAG_ECCD) && READ_REG(FLASH->ECCDR) == 0xFFFF); - - __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ECCD); - } - } + , HighCycleAreaOrOtpIrqHandler() {} } diff --git a/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.hpp b/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.hpp index e23102d2..625228b8 100644 --- a/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.hpp +++ b/hal_st/stm32fxxx/FlashInternalHighCycleAreaStm.hpp @@ -1,12 +1,11 @@ -// Copyright (c) 2024 Koninklijke Philips N.V. #ifndef HAL_FLASH_INTERNAL_HIGH_CYCLE_AREA_STM_HPP #define HAL_FLASH_INTERNAL_HIGH_CYCLE_AREA_STM_HPP -#include DEVICE_HEADER #include "hal/interfaces/Flash.hpp" -#include "hal_st/cortex/InterruptCortex.hpp" +#include "hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp" #include "infra/util/MemoryRange.hpp" #include +#include DEVICE_HEADER namespace hal { @@ -19,7 +18,7 @@ namespace hal class WithIrqHandler; - FlashInternalHighCycleAreaStm(uint32_t bank = FLASH_BANK_2); + explicit FlashInternalHighCycleAreaStm(uint32_t bank = FLASH_BANK_2); void WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function onDone) override; void ReadBuffer(infra::ByteRange buffer, uint32_t address, infra::Function onDone) override; @@ -37,12 +36,10 @@ namespace hal class FlashInternalHighCycleAreaStm::WithIrqHandler : public FlashInternalHighCycleAreaStm + , private HighCycleAreaOrOtpIrqHandler { public: - WithIrqHandler(uint32_t bank = FLASH_BANK_2); - - private: - hal::ImmediateInterruptHandler nmi; + explicit WithIrqHandler(uint32_t bank = FLASH_BANK_2); }; } diff --git a/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.cpp b/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.cpp new file mode 100644 index 00000000..476dd85f --- /dev/null +++ b/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.cpp @@ -0,0 +1,24 @@ +#include "hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp" +#include "infra/util/ReallyAssert.hpp" +#include DEVICE_HEADER + +namespace hal +{ + HighCycleAreaOrOtpIrqHandler::HighCycleAreaOrOtpIrqHandler() + : nmi{ + IRQn_Type::NonMaskableInt_IRQn, [] + { + // From RM0481 Rev 4: + // Section 7.3.4 FLASH read operations: + // If the application reads an OTP data or flash high-cycle data not previously written, a + // double ECC error is reported and only a word full of set bits is returned (see + // Section (fixed:) 7.9.10 for details). The read data (in 16 bits) is stored in FLASH_ECCDR + // register, so that the user can identify if the double ECC error is due to a virgin data or a + // real ECC error. + really_assert(__HAL_FLASH_GET_FLAG(FLASH_FLAG_ECCD) && READ_REG(FLASH->ECCDR) == 0xFFFF); + + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ECCD); + } + } + {} +} diff --git a/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp b/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp new file mode 100644 index 00000000..b7e4ce70 --- /dev/null +++ b/hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp @@ -0,0 +1,22 @@ +#ifndef HALST_HIGH_CYCLE_AREA_OR_OTP_IRQ_HANDLER_HPP +#define HALST_HIGH_CYCLE_AREA_OR_OTP_IRQ_HANDLER_HPP + +#include "hal_st/cortex/InterruptCortex.hpp" + +namespace hal +{ + // This class is used as a base class for both FlashInternalHighCycleAreaStm::WithIrqHandler and OneTimeProgrammableFlashStm::WithIrqHandler, + // as they share the same NMI handler which is responsible for clearing the flash end of operation flag. + class HighCycleAreaOrOtpIrqHandler + { + public: + HighCycleAreaOrOtpIrqHandler(); + HighCycleAreaOrOtpIrqHandler(const HighCycleAreaOrOtpIrqHandler&) = delete; + HighCycleAreaOrOtpIrqHandler& operator=(const HighCycleAreaOrOtpIrqHandler&) = delete; + + private: + hal::ImmediateInterruptHandler nmi; + }; +} + +#endif diff --git a/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.cpp b/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.cpp new file mode 100644 index 00000000..21fc6a0f --- /dev/null +++ b/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.cpp @@ -0,0 +1,120 @@ +#include "hal_st/stm32fxxx/OneTimeProgrammableFlashStm.hpp" +#include "hal_st/stm32fxxx/FlashInternalStmDetail.hpp" +#include "infra/event/EventDispatcher.hpp" +#include "infra/util/LogAndAbort.hpp" +#include "infra/util/ReallyAssert.hpp" +#include +#include +#include +#include DEVICE_HEADER + +namespace +{ + using ConstHalfWordRange = hal::OneTimeProgrammableFlashWorker::ConstHalfWordRange; + + template + constexpr auto ptr() + { + return reinterpret_cast(address); + } + + constexpr size_t halfWordSize = sizeof(uint16_t); + constexpr uint32_t sectorNumber{ 32 }; + constexpr uint32_t sectorSize{ FLASH_OTP_SIZE / sectorNumber }; + const ConstHalfWordRange OtpRange{ ptr(), ptr() }; + + void Copy(const uint16_t* begin, const uint16_t* end, uint16_t* destination) + { + for (; begin != end; ++destination, ++begin) + *destination = *begin; + } +} + +namespace hal +{ + OneTimeProgrammableFlashWorker::OneTimeProgrammableFlashWorker() + : flashMemory{ OtpRange } + {} + + void OneTimeProgrammableFlashWorker::ReadBuffer(infra::ByteRange buffer, uint32_t address) + { + really_assert(buffer.size() % sizeof(uint16_t) == 0); + // address is byte-based, addressAdjusted is half-word-based + auto addressAdjusted = address / halfWordSize; + auto destination = infra::ReinterpretCastMemoryRange(buffer); + // explicitely copy data uint16_t aligned and prevent gcc from (falsely) optimizing to memmove which will reinterpret as arrays of uint8_t + Copy(flashMemory.begin() + addressAdjusted, flashMemory.begin() + addressAdjusted + destination.size(), destination.begin()); + } + + void OneTimeProgrammableFlashWorker::WriteBuffer(infra::ConstByteRange buffer, uint32_t address) + { + AssertDestinationIsUntouched(buffer, address); + + HAL_FLASH_Unlock(); + + detail::AlignedWriteBuffer(buffer, address, reinterpret_cast(flashMemory.begin())); + + HAL_FLASH_Lock(); + } + + [[noreturn]] void OneTimeProgrammableFlashWorker::EraseSectors([[maybe_unused]] uint32_t beginIndex, [[maybe_unused]] uint32_t endIndex) + { + // OTP cannot be erased + LOG_AND_ABORT("Not supported"); + } + + uint32_t OneTimeProgrammableFlashWorker::SectorNumber() + { + return sectorNumber; + } + + uint32_t OneTimeProgrammableFlashWorker::SectorSize() + { + return sectorSize; + } + + infra::MemoryRange OneTimeProgrammableFlashWorker::Range() + { + auto* base = reinterpret_cast(FLASH_OTP_BASE); + auto* end = reinterpret_cast(FLASH_OTP_BASE + FLASH_OTP_SIZE); + return infra::MakeRange(base, end); + } + + void OneTimeProgrammableFlashWorker::AssertDestinationIsUntouched(infra::ConstByteRange buffer, uint32_t address) + { + auto writeBuffer = infra::ReinterpretCastMemoryRange(buffer); + auto otpDestination = infra::Head(infra::DiscardHead(Range(), address / halfWordSize), writeBuffer.size()); + // From RM0481 Rev 4: + // Section 7.3.9 OTP and RO memory access: + // Overwriting an already programmed 16-bit half-word can lead to data and ECC errors, + // and is therefore not supported. + for (size_t offset = 0; const auto& halfWord : otpDestination) + { + really_assert(halfWord == 0xFFFF || halfWord == writeBuffer[offset]); + ++offset; + } + } + + OneTimeProgrammableFlashStm::OneTimeProgrammableFlashStm() + : FlashHomogeneous{ OneTimeProgrammableFlashWorker::SectorNumber(), OneTimeProgrammableFlashWorker::SectorSize() } + , OneTimeProgrammableFlashWorker{} + {} + + void OneTimeProgrammableFlashStm::ReadBuffer(infra::ByteRange buffer, uint32_t address, infra::Function onDone) + { + OneTimeProgrammableFlashWorker::ReadBuffer(buffer, address); + infra::EventDispatcher::Instance().Schedule(onDone); + } + + void OneTimeProgrammableFlashStm::WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function onDone) + { + OneTimeProgrammableFlashWorker::WriteBuffer(buffer, address); + infra::EventDispatcher::Instance().Schedule(onDone); + } + + void OneTimeProgrammableFlashStm::EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function onDone) + { + OneTimeProgrammableFlashWorker::EraseSectors(beginIndex, endIndex); + infra::EventDispatcher::Instance().Schedule(onDone); + } +} diff --git a/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.hpp b/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.hpp new file mode 100644 index 00000000..0b55de43 --- /dev/null +++ b/hal_st/stm32fxxx/OneTimeProgrammableFlashStm.hpp @@ -0,0 +1,57 @@ +#ifndef HALST_ONE_TIME_PROGRAMMABLE_FLASH_STM_HPP +#define HALST_ONE_TIME_PROGRAMMABLE_FLASH_STM_HPP + +#include "hal/interfaces/FlashHomogeneous.hpp" +#include "hal_st/stm32fxxx/HighCycleAreaOrOtpIrqHandler.hpp" +#include + +namespace hal +{ + // This class is used to implement both the synchronous and asynchronous version of the OTP flash, + // as the underlying implementation is the same except for scheduling the onDone callback. + class OneTimeProgrammableFlashWorker + { + public: + using ConstHalfWordRange = infra::MemoryRange; + + class WithIrqHandler; + + OneTimeProgrammableFlashWorker(); + + void WriteBuffer(infra::ConstByteRange buffer, uint32_t address); + void ReadBuffer(infra::ByteRange buffer, uint32_t address); + [[noreturn]] void EraseSectors(uint32_t beginIndex, uint32_t endIndex); + + static uint32_t SectorNumber(); + static uint32_t SectorSize(); + static infra::MemoryRange Range(); + + private: + ConstHalfWordRange flashMemory; + void AssertDestinationIsUntouched(infra::ConstByteRange buffer, uint32_t address); + }; + + class OneTimeProgrammableFlashStm + : public FlashHomogeneous + , private OneTimeProgrammableFlashWorker + { + public: + class WithIrqHandler; + + OneTimeProgrammableFlashStm(); + + // implementation of Flash interface + void WriteBuffer(infra::ConstByteRange buffer, uint32_t address, infra::Function onDone) override; + void ReadBuffer(infra::ByteRange buffer, uint32_t address, infra::Function onDone) override; + void EraseSectors(uint32_t beginIndex, uint32_t endIndex, infra::Function onDone) override; + + using OneTimeProgrammableFlashWorker::Range; + }; + + class OneTimeProgrammableFlashStm::WithIrqHandler + : public OneTimeProgrammableFlashStm + , private HighCycleAreaOrOtpIrqHandler + {}; +} + +#endif diff --git a/hal_st/synchronous_stm32fxxx/CMakeLists.txt b/hal_st/synchronous_stm32fxxx/CMakeLists.txt index 0fb59d6f..e508ecd3 100644 --- a/hal_st/synchronous_stm32fxxx/CMakeLists.txt +++ b/hal_st/synchronous_stm32fxxx/CMakeLists.txt @@ -19,6 +19,8 @@ target_sources(hal_st.synchronous_stm32fxxx PRIVATE SynchronousFlashInternalStm.hpp $<$:SynchronousHardwareSemaphoreStm.cpp> $<$:SynchronousHardwareSemaphoreStm.hpp> + $<$:SynchronousOneTimeProgrammableFlashStm.cpp> + $<$:SynchronousOneTimeProgrammableFlashStm.hpp> SynchronousRandomDataGeneratorStm.cpp SynchronousRandomDataGeneratorStm.hpp SynchronousSpiMasterStm.cpp diff --git a/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.cpp b/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.cpp new file mode 100644 index 00000000..2ac89435 --- /dev/null +++ b/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.cpp @@ -0,0 +1,24 @@ +#include "hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.hpp" + +namespace hal +{ + SynchronousOneTimeProgrammableFlashStm::SynchronousOneTimeProgrammableFlashStm() + : SynchronousFlashHomogeneous{ OneTimeProgrammableFlashWorker::SectorNumber(), OneTimeProgrammableFlashWorker::SectorSize() } + , OneTimeProgrammableFlashWorker{} + {} + + void SynchronousOneTimeProgrammableFlashStm::ReadBuffer(infra::ByteRange buffer, uint32_t address) + { + OneTimeProgrammableFlashWorker::ReadBuffer(buffer, address); + } + + void SynchronousOneTimeProgrammableFlashStm::WriteBuffer(infra::ConstByteRange buffer, uint32_t address) + { + OneTimeProgrammableFlashWorker::WriteBuffer(buffer, address); + } + + void SynchronousOneTimeProgrammableFlashStm::EraseSectors(uint32_t beginIndex, uint32_t endIndex) + { + OneTimeProgrammableFlashWorker::EraseSectors(beginIndex, endIndex); + } +} diff --git a/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.hpp b/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.hpp new file mode 100644 index 00000000..f54f1d65 --- /dev/null +++ b/hal_st/synchronous_stm32fxxx/SynchronousOneTimeProgrammableFlashStm.hpp @@ -0,0 +1,32 @@ +#ifndef HALST_SYNCHRONOUS_ONE_TIME_PROGRAMMABLE_FLASH_STM_HPP +#define HALST_SYNCHRONOUS_ONE_TIME_PROGRAMMABLE_FLASH_STM_HPP + +#include "hal/synchronous_interfaces/SynchronousFlashHomogeneous.hpp" +#include "hal_st/stm32fxxx/OneTimeProgrammableFlashStm.hpp" + +namespace hal +{ + class SynchronousOneTimeProgrammableFlashStm + : public SynchronousFlashHomogeneous + , private OneTimeProgrammableFlashWorker + { + public: + class WithIrqHandler; + + SynchronousOneTimeProgrammableFlashStm(); + + // implementation of SynchronousFlash interface + void WriteBuffer(infra::ConstByteRange buffer, uint32_t address) override; + void ReadBuffer(infra::ByteRange buffer, uint32_t address) override; + void EraseSectors(uint32_t beginIndex, uint32_t endIndex) override; + + using OneTimeProgrammableFlashWorker::Range; + }; + + class SynchronousOneTimeProgrammableFlashStm::WithIrqHandler + : public SynchronousOneTimeProgrammableFlashStm + , private HighCycleAreaOrOtpIrqHandler + {}; +} + +#endif