Skip to content

Commit 79ea883

Browse files
authored
New flash writing method with offset/memory/size alignment handling (#7514)
* Do not write more data than requested on PUYA flashes * Always align flash reads/writes to 4 bytes * fixup! Always align flash reads/writes to 4 bytes This commit simplifies the code a bit and fixes a bug that caused wrong number of bytes to be written * fixup! Always align flash reads/writes to 4 bytes * fixup! Always align flash reads/writes to 4 bytes * Check for result before additional read/write * Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * fixup! Add overloads for unaligned reads/writes * Add tests for flashRead/flashWrite * fixup! Add overloads for unaligned reads/writes * fixup! Add tests for flashRead/flashWrite * fixup! Add tests for flashRead/flashWrite * fixup! Add overloads for unaligned reads/writes
1 parent 200e47f commit 79ea883

File tree

7 files changed

+529
-151
lines changed

7 files changed

+529
-151
lines changed

cores/esp8266/Esp.cpp

+234-20
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ extern struct rst_info resetInfo;
4242
#ifndef PUYA_SUPPORT
4343
#define PUYA_SUPPORT 1
4444
#endif
45-
#ifndef PUYA_BUFFER_SIZE
46-
// Good alternative for buffer size is: SPI_FLASH_SEC_SIZE (= 4k)
47-
// Always use a multiple of flash page size (256 bytes)
48-
#define PUYA_BUFFER_SIZE 256
49-
#endif
5045

5146
/**
5247
* User-defined Literals
@@ -668,11 +663,14 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
668663
if (data == nullptr) {
669664
return SPI_FLASH_RESULT_ERR;
670665
}
666+
if (size % 4 != 0) {
667+
return SPI_FLASH_RESULT_ERR;
668+
}
671669
// PUYA flash chips need to read existing data, update in memory and write modified data again.
672670
static uint32_t *flash_write_puya_buf = nullptr;
673671

674672
if (flash_write_puya_buf == nullptr) {
675-
flash_write_puya_buf = (uint32_t*) malloc(PUYA_BUFFER_SIZE);
673+
flash_write_puya_buf = (uint32_t*) malloc(FLASH_PAGE_SIZE);
676674
// No need to ever free this, since the flash chip will never change at runtime.
677675
if (flash_write_puya_buf == nullptr) {
678676
// Memory could not be allocated.
@@ -686,45 +684,261 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
686684
uint32_t pos = offset;
687685
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
688686
size_t bytesNow = bytesLeft;
689-
if (bytesNow > PUYA_BUFFER_SIZE) {
690-
bytesNow = PUYA_BUFFER_SIZE;
691-
bytesLeft -= PUYA_BUFFER_SIZE;
687+
if (bytesNow > FLASH_PAGE_SIZE) {
688+
bytesNow = FLASH_PAGE_SIZE;
689+
bytesLeft -= FLASH_PAGE_SIZE;
692690
} else {
693691
bytesLeft = 0;
694692
}
695-
size_t bytesAligned = (bytesNow + 3) & ~3;
696-
rc = spi_flash_read(pos, flash_write_puya_buf, bytesAligned);
693+
rc = spi_flash_read(pos, flash_write_puya_buf, bytesNow);
697694
if (rc != SPI_FLASH_RESULT_OK) {
698695
return rc;
699696
}
700-
for (size_t i = 0; i < bytesAligned / 4; ++i) {
697+
for (size_t i = 0; i < bytesNow / 4; ++i) {
701698
flash_write_puya_buf[i] &= *ptr;
702699
++ptr;
703700
}
704-
rc = spi_flash_write(pos, flash_write_puya_buf, bytesAligned);
701+
rc = spi_flash_write(pos, flash_write_puya_buf, bytesNow);
705702
pos += bytesNow;
706703
}
707704
return rc;
708705
}
709706
#endif
710707

711-
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size) {
708+
bool EspClass::flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount) {
709+
uint32_t alignedAddress = (address & ~3);
710+
uint32_t alignmentOffset = address - alignedAddress;
711+
712+
if (alignedAddress != ((address + byteCount - 1) & ~3)) {
713+
// Only one 4 byte block is supported
714+
return false;
715+
}
716+
#if PUYA_SUPPORT
717+
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
718+
uint8_t tempData[4] __attribute__((aligned(4)));
719+
if (spi_flash_read(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
720+
return false;
721+
}
722+
for (size_t i = 0; i < byteCount; i++) {
723+
tempData[i + alignmentOffset] &= value[i];
724+
}
725+
if (spi_flash_write(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
726+
return false;
727+
}
728+
}
729+
else
730+
#endif // PUYA_SUPPORT
731+
{
732+
uint32_t tempData;
733+
if (spi_flash_read(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
734+
return false;
735+
}
736+
memcpy((uint8_t *)&tempData + alignmentOffset, value, byteCount);
737+
if (spi_flash_write(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
738+
return false;
739+
}
740+
}
741+
return true;
742+
}
743+
744+
size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size) {
745+
size_t sizeLeft = (size & ~3);
746+
size_t currentOffset = 0;
747+
// Memory is unaligned, so we need to copy it to an aligned buffer
748+
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
749+
// Handle page boundary
750+
bool pageBreak = ((address % 4) != 0) && ((address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
751+
752+
if (pageBreak) {
753+
size_t byteCount = 4 - (address % 4);
754+
755+
if (!flashReplaceBlock(address, data, byteCount)) {
756+
return 0;
757+
}
758+
// We will now have aligned address, so we can cross page boundaries
759+
currentOffset += byteCount;
760+
// Realign size to 4
761+
sizeLeft = (size - byteCount) & ~3;
762+
}
763+
764+
while (sizeLeft) {
765+
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
766+
memcpy(alignedData, data + currentOffset, willCopy);
767+
// We now have address, data and size aligned to 4 bytes, so we can use aligned write
768+
if (!flashWrite(address + currentOffset, alignedData, willCopy))
769+
{
770+
return 0;
771+
}
772+
sizeLeft -= willCopy;
773+
currentOffset += willCopy;
774+
}
775+
776+
return currentOffset;
777+
}
778+
779+
bool EspClass::flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size) {
780+
if (size > 4) {
781+
return false;
782+
}
783+
size_t pageLeft = FLASH_PAGE_SIZE - (address % FLASH_PAGE_SIZE);
784+
size_t offset = 0;
785+
size_t sizeLeft = size;
786+
if (pageLeft > 3) {
787+
return false;
788+
}
789+
790+
if (!flashReplaceBlock(address, data, pageLeft)) {
791+
return false;
792+
}
793+
offset += pageLeft;
794+
sizeLeft -= pageLeft;
795+
// We replaced last 4-byte block of the page, now we write the remainder in next page
796+
if (!flashReplaceBlock(address + offset, data + offset, sizeLeft)) {
797+
return false;
798+
}
799+
return true;
800+
}
801+
802+
bool EspClass::flashWrite(uint32_t address, const uint32_t *data, size_t size) {
712803
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
804+
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + size - 1) / FLASH_PAGE_SIZE));
805+
806+
if ((uintptr_t)data % 4 != 0 || size % 4 != 0 || pageBreak) {
807+
return false;
808+
}
713809
#if PUYA_SUPPORT
714810
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
715-
rc = spi_flash_write_puya(offset, data, size);
811+
rc = spi_flash_write_puya(address, const_cast<uint32_t *>(data), size);
716812
}
717813
else
718-
#endif
814+
#endif // PUYA_SUPPORT
719815
{
720-
rc = spi_flash_write(offset, data, size);
816+
rc = spi_flash_write(address, const_cast<uint32_t *>(data), size);
721817
}
722818
return rc == SPI_FLASH_RESULT_OK;
723819
}
724820

725-
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size) {
726-
auto rc = spi_flash_read(offset, (uint32_t*) data, size);
727-
return rc == SPI_FLASH_RESULT_OK;
821+
bool EspClass::flashWrite(uint32_t address, const uint8_t *data, size_t size) {
822+
if (size == 0) {
823+
return true;
824+
}
825+
826+
size_t sizeLeft = size & ~3;
827+
size_t currentOffset = 0;
828+
829+
if (sizeLeft) {
830+
if ((uintptr_t)data % 4 != 0) {
831+
size_t written = flashWriteUnalignedMemory(address, data, size);
832+
if (!written) {
833+
return false;
834+
}
835+
currentOffset += written;
836+
sizeLeft -= written;
837+
} else {
838+
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
839+
840+
if (pageBreak) {
841+
while (sizeLeft) {
842+
// We cannot cross page boundary, but the write must be 4 byte aligned,
843+
// so this is the maximum amount we can write
844+
size_t pageBoundary = (FLASH_PAGE_SIZE - ((address + currentOffset) % FLASH_PAGE_SIZE)) & ~3;
845+
846+
if (sizeLeft > pageBoundary) {
847+
// Aligned write up to page boundary
848+
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), pageBoundary)) {
849+
return false;
850+
}
851+
currentOffset += pageBoundary;
852+
sizeLeft -= pageBoundary;
853+
// Cross the page boundary
854+
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, 4)) {
855+
return false;
856+
}
857+
currentOffset += 4;
858+
sizeLeft -= 4;
859+
} else {
860+
// We do not cross page boundary
861+
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), sizeLeft)) {
862+
return false;
863+
}
864+
currentOffset += sizeLeft;
865+
sizeLeft = 0;
866+
}
867+
}
868+
} else {
869+
// Pointer is properly aligned and write does not cross page boundary,
870+
// so use aligned write
871+
if (!flashWrite(address, (uint32_t *)data, sizeLeft)) {
872+
return false;
873+
}
874+
currentOffset = sizeLeft;
875+
sizeLeft = 0;
876+
}
877+
}
878+
}
879+
sizeLeft = size - currentOffset;
880+
if (sizeLeft > 0) {
881+
// Size was not aligned, so we have some bytes left to write, we also need to recheck for
882+
// page boundary crossing
883+
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
884+
885+
if (pageBreak) {
886+
// Cross the page boundary
887+
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, sizeLeft)) {
888+
return false;
889+
}
890+
} else {
891+
// Just write partial block
892+
flashReplaceBlock(address + currentOffset, data + currentOffset, sizeLeft);
893+
}
894+
}
895+
896+
return true;
897+
}
898+
899+
bool EspClass::flashRead(uint32_t address, uint8_t *data, size_t size) {
900+
size_t sizeAligned = size & ~3;
901+
size_t currentOffset = 0;
902+
903+
if ((uintptr_t)data % 4 != 0) {
904+
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
905+
size_t sizeLeft = sizeAligned;
906+
907+
while (sizeLeft) {
908+
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
909+
// We read to our aligned buffer and then copy to data
910+
if (!flashRead(address + currentOffset, alignedData, willCopy))
911+
{
912+
return false;
913+
}
914+
memcpy(data + currentOffset, alignedData, willCopy);
915+
sizeLeft -= willCopy;
916+
currentOffset += willCopy;
917+
}
918+
} else {
919+
// Pointer is properly aligned, so use aligned read
920+
if (!flashRead(address, (uint32_t *)data, sizeAligned)) {
921+
return false;
922+
}
923+
currentOffset = sizeAligned;
924+
}
925+
926+
if (currentOffset < size) {
927+
uint32_t tempData;
928+
if (spi_flash_read(address + currentOffset, &tempData, 4) != SPI_FLASH_RESULT_OK) {
929+
return false;
930+
}
931+
memcpy((uint8_t *)data + currentOffset, &tempData, size - currentOffset);
932+
}
933+
934+
return true;
935+
}
936+
937+
bool EspClass::flashRead(uint32_t address, uint32_t *data, size_t size) {
938+
if ((uintptr_t)data % 4 != 0 || size % 4 != 0) {
939+
return false;
940+
}
941+
return (spi_flash_read(address, data, size) == SPI_FLASH_RESULT_OK);
728942
}
729943

730944
String EspClass::getSketchMD5()

cores/esp8266/Esp.h

+76-2
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,48 @@ class EspClass {
150150
bool checkFlashCRC();
151151

152152
bool flashEraseSector(uint32_t sector);
153-
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
154-
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
153+
/**
154+
* @brief Write @a size bytes from @a data to flash at @a address
155+
* This overload requires @a data and @a size to be always 4 byte aligned and
156+
* @a address to be 4 byte aligned if the write crossess page boundary,
157+
* but guarantees no overhead (except on PUYA flashes)
158+
* @param address address on flash where write should start, 4 byte alignment is conditional
159+
* @param data input buffer, must be 4-byte aligned
160+
* @param size amount of data, must be a multiple of 4
161+
* @return bool result of operation
162+
* @retval true success
163+
* @retval false failure to write to flash or incorrect alignment of params
164+
*/
165+
bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
166+
/**
167+
* @brief Write @a size bytes from @a data to flash at @a address
168+
* This overload handles all misalignment cases
169+
* @param address address on flash where write should start
170+
* @param data input buffer, passing unaligned memory will cause significant stack usage
171+
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
172+
* @return bool result of operation
173+
*/
174+
bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
175+
/**
176+
* @brief Read @a size bytes to @a data to flash at @a address
177+
* This overload requires @a data and @a size to be 4 byte aligned
178+
* @param address address on flash where read should start
179+
* @param data input buffer, must be 4-byte aligned
180+
* @param size amount of data, must be a multiple of 4
181+
* @return bool result of operation
182+
* @retval true success
183+
* @retval false failure to read from flash or incorrect alignment of params
184+
*/
185+
bool flashRead(uint32_t address, uint32_t *data, size_t size);
186+
/**
187+
* @brief Read @a size bytes to @a data to flash at @a address
188+
* This overload handles all misalignment cases
189+
* @param address address on flash where read should start
190+
* @param data input buffer, passing unaligned memory will cause significant stack usage
191+
* @param size amount of data, passing not multiple of 4 will cause additional read
192+
* @return bool result of operation
193+
*/
194+
bool flashRead(uint32_t address, uint8_t *data, size_t size);
155195

156196
uint32_t getSketchSize();
157197
String getSketchMD5();
@@ -175,6 +215,40 @@ class EspClass {
175215
#else
176216
uint32_t getCycleCount();
177217
#endif // !defined(CORE_MOCK)
218+
private:
219+
/**
220+
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
221+
*
222+
* @param address flash address
223+
* @param value buffer with data
224+
* @param byteCount number of bytes to replace
225+
* @return bool result of operation
226+
* @retval true success
227+
* @retval false failed to read/write or invalid args
228+
*/
229+
bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
230+
/**
231+
* @brief Write up to @a size bytes from @a data to flash at @a address
232+
* This function takes case of unaligned memory acces by copying @a data to a temporary buffer,
233+
* it also takes care of page boundary crossing see @a flashWritePageBreak as to why it's done.
234+
* Less than @a size bytes may be written, due to 4 byte alignment requirement of spi_flash_write
235+
* @param address address on flash where write should start
236+
* @param data input buffer
237+
* @param size amount of data
238+
* @return size_t amount of data written, 0 on failure
239+
*/
240+
size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
241+
/**
242+
* @brief Splits up to 4 bytes into 4 byte blocks and writes them to flash
243+
* We need this since spi_flash_write cannot handle writing over a page boundary with unaligned offset
244+
* i.e. spi_flash_write(254, data, 4) will fail, also we cannot write less bytes as in
245+
* spi_flash_write(254, data, 2) since it will be extended internally to 4 bytes and fail
246+
* @param address start of write
247+
* @param data data to be written
248+
* @param size amount of data, must be < 4
249+
* @return bool result of operation
250+
*/
251+
bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
178252
};
179253

180254
extern EspClass ESP;

0 commit comments

Comments
 (0)