@@ -673,7 +673,46 @@ bool EspClass::flashEraseSector(uint32_t sector) {
673673 return rc == 0 ;
674674}
675675
676+ // Adapted from the old version of `flash_hal_write()` (before 3.0.0), which was used for SPIFFS to allow
677+ // writing from both unaligned u8 buffers and to an unaligned offset on flash.
678+ // Updated version re-uses some of the code from RTOS, replacing individual methods for block & page
679+ // writes with just a single one
680+ // https://github.com/espressif/ESP8266_RTOS_SDK/blob/master/components/spi_flash/src/spi_flash.c
681+ // (if necessary, we could follow the esp-idf code and introduce flash chip drivers controling more than just writing methods?)
682+
683+ // This is a generic writer that does not cross page boundaries.
684+ // Offset, data address and size *must* be 4byte aligned.
685+ static SpiFlashOpResult spi_flash_write_page_break (uint32_t offset, uint32_t *data, size_t size) {
686+ static constexpr uint32_t PageSize { FLASH_PAGE_SIZE };
687+ size_t size_page_aligned = PageSize - (offset % PageSize);
688+
689+ // most common case, we don't cross a page and simply write the data
690+ if (size < size_page_aligned) {
691+ return spi_flash_write (offset, data, size);
692+ }
693+
694+ // otherwise, write the initial part and continue writing breaking each page interval
695+ SpiFlashOpResult result = SPI_FLASH_RESULT_ERR;
696+ if ((result = spi_flash_write (offset, data, size_page_aligned)) != SPI_FLASH_RESULT_OK) {
697+ return result;
698+ }
699+
700+ const auto last_page = (size - size_page_aligned) / PageSize;
701+ for (uint32_t page = 0 ; page < last_page; ++page) {
702+ if ((result = spi_flash_write (offset + size_page_aligned, data + (size_page_aligned >> 2 ), PageSize)) != SPI_FLASH_RESULT_OK) {
703+ return result;
704+ }
705+
706+ size_page_aligned += PageSize;
707+ }
708+
709+ // finally, the remaining data
710+ return spi_flash_write (offset + size_page_aligned, data + (size_page_aligned >> 2 ), size - size_page_aligned);
711+ }
712+
676713#if PUYA_SUPPORT
714+ // Special wrapper for spi_flash_write *only for PUYA flash chips*
715+ // Already handles paging, could be used as a `spi_flash_write_page_break` replacement
677716static SpiFlashOpResult spi_flash_write_puya (uint32_t offset, uint32_t *data, size_t size) {
678717 if (data == nullptr ) {
679718 return SPI_FLASH_RESULT_ERR;
@@ -720,230 +759,165 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
720759}
721760#endif
722761
723- bool EspClass::flashReplaceBlock (uint32_t address, const uint8_t *value, uint32_t byteCount) {
724- uint32_t alignedAddress = (address & ~3 );
725- uint32_t alignmentOffset = address - alignedAddress;
762+ static constexpr size_t Alignment { 4 };
726763
727- if (alignedAddress != ((address + byteCount - 1 ) & ~3 )) {
728- // Only one 4 byte block is supported
729- return false ;
730- }
731- #if PUYA_SUPPORT
732- if (getFlashChipVendorId () == SPI_FLASH_VENDOR_PUYA) {
733- uint8_t tempData[4 ] __attribute__ ((aligned (4 )));
734- if (spi_flash_read (alignedAddress, (uint32_t *)tempData, 4 ) != SPI_FLASH_RESULT_OK) {
735- return false ;
736- }
737- for (size_t i = 0 ; i < byteCount; i++) {
738- tempData[i + alignmentOffset] &= value[i];
739- }
740- if (spi_flash_write (alignedAddress, (uint32_t *)tempData, 4 ) != SPI_FLASH_RESULT_OK) {
741- return false ;
742- }
743- }
744- else
745- #endif // PUYA_SUPPORT
746- {
747- uint32_t tempData;
748- if (spi_flash_read (alignedAddress, &tempData, 4 ) != SPI_FLASH_RESULT_OK) {
749- return false ;
750- }
751- memcpy ((uint8_t *)&tempData + alignmentOffset, value, byteCount);
752- if (spi_flash_write (alignedAddress, &tempData, 4 ) != SPI_FLASH_RESULT_OK) {
753- return false ;
754- }
755- }
756- return true ;
764+ template <typename T>
765+ static T aligned (T value) {
766+ static constexpr auto Mask = Alignment - 1 ;
767+ return (value + Mask) & ~Mask;
768+ }
769+
770+ template <typename T>
771+ static T alignBefore (T value) {
772+ return aligned (value) - Alignment;
773+ }
774+
775+ static bool isAlignedAddress (uint32_t address) {
776+ return (address & (Alignment - 1 )) == 0 ;
777+ }
778+
779+ static bool isAlignedSize (size_t size) {
780+ return (size & (Alignment - 1 )) == 0 ;
781+ }
782+
783+ static bool isAlignedPointer (const uint8_t *ptr) {
784+ return isAlignedAddress (reinterpret_cast <uint32_t >(ptr));
757785}
758786
787+
759788size_t EspClass::flashWriteUnalignedMemory (uint32_t address, const uint8_t *data, size_t size) {
760- size_t sizeLeft = (size & ~3 );
761- size_t currentOffset = 0 ;
762- // Memory is unaligned, so we need to copy it to an aligned buffer
763- uint32_t alignedData[FLASH_PAGE_SIZE / sizeof (uint32_t )] __attribute__ ((aligned (4 )));
764- // Handle page boundary
765- bool pageBreak = ((address % 4 ) != 0 ) && ((address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1 ) / FLASH_PAGE_SIZE));
789+ auto flash_write = [](uint32_t address, uint8_t *data, size_t size) {
790+ return spi_flash_write (address, reinterpret_cast <uint32_t *>(data), size) == SPI_FLASH_RESULT_OK;
791+ };
792+
793+ auto flash_read = [](uint32_t address, uint8_t *data, size_t size) {
794+ return spi_flash_read (address, reinterpret_cast <uint32_t *>(data), size) == SPI_FLASH_RESULT_OK;
795+ };
766796
767- if (pageBreak) {
768- size_t byteCount = 4 - (address % 4 ) ;
797+ constexpr size_t BufferSize { FLASH_PAGE_SIZE };
798+ alignas ( alignof ( uint32_t )) uint8_t buf[BufferSize] ;
769799
770- if (!flashReplaceBlock (address, data, byteCount)) {
800+ size_t written = 0 ;
801+
802+ if (!isAlignedAddress (address)) {
803+ auto before_address = alignBefore (address);
804+ auto offset = address - before_address;
805+ auto wlen = std::min (Alignment - offset, size);
806+
807+ if (!flash_read (before_address, &buf[0 ], Alignment)) {
771808 return 0 ;
772809 }
773- // We will now have aligned address, so we can cross page boundaries
774- currentOffset += byteCount;
775- // Realign size to 4
776- sizeLeft = (size - byteCount) & ~3 ;
777- }
778810
779- while (sizeLeft) {
780- size_t willCopy = std::min (sizeLeft, sizeof (alignedData));
781- memcpy (alignedData, data + currentOffset, willCopy);
782- // We now have address, data and size aligned to 4 bytes, so we can use aligned write
783- if (!flashWrite (address + currentOffset, alignedData, willCopy))
784- {
811+ #if PUYA_SUPPORT
812+ if (getFlashChipVendorId () == SPI_FLASH_VENDOR_PUYA) {
813+ for (size_t i = 0 ; i < wlen ; ++i) {
814+ buf[offset + i] &= data[i];
815+ }
816+ } else {
817+ #endif
818+ memcpy (&buf[offset], data, wlen);
819+ #if PUYA_SUPPORT
820+ }
821+ #endif
822+
823+ if (!flash_write (before_address, &buf[0 ], Alignment)) {
785824 return 0 ;
786825 }
787- sizeLeft -= willCopy;
788- currentOffset += willCopy;
826+
827+ address += wlen;
828+ data += wlen;
829+ written += wlen;
830+ size -= wlen;
789831 }
790832
791- return currentOffset;
792- }
833+ while (size > 0 ) {
834+ auto len = std::min (size, BufferSize);
835+ auto wlen = aligned (len);
793836
794- bool EspClass::flashWritePageBreak (uint32_t address, const uint8_t *data, size_t size) {
795- if (size > 4 ) {
796- return false ;
797- }
798- size_t pageLeft = FLASH_PAGE_SIZE - (address % FLASH_PAGE_SIZE);
799- size_t offset = 0 ;
800- size_t sizeLeft = size;
801- if (pageLeft > 3 ) {
802- return false ;
803- }
837+ if (wlen != len) {
838+ auto partial = wlen - Alignment;
839+ if (!flash_read (address + partial, &buf[partial], Alignment)) {
840+ return written;
841+ }
842+ }
804843
805- if (!flashReplaceBlock (address, data, pageLeft)) {
806- return false ;
807- }
808- offset += pageLeft;
809- sizeLeft -= pageLeft;
810- // We replaced last 4-byte block of the page, now we write the remainder in next page
811- if (!flashReplaceBlock (address + offset, data + offset, sizeLeft)) {
812- return false ;
844+ memcpy (&buf[0 ], data, len);
845+ if (!flashWrite (address, reinterpret_cast <const uint32_t *>(&buf[0 ]), wlen)) {
846+ return written;
847+ }
848+
849+ address += len;
850+ data += len;
851+ written += len;
852+ size -= len;
813853 }
814- return true ;
854+
855+ return written;
815856}
816857
817858bool EspClass::flashWrite (uint32_t address, const uint32_t *data, size_t size) {
818- SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
819- bool pageBreak = ((address % 4 ) != 0 && (address / FLASH_PAGE_SIZE) != ((address + size - 1 ) / FLASH_PAGE_SIZE));
820-
821- if ((uintptr_t )data % 4 != 0 || size % 4 != 0 || pageBreak) {
822- return false ;
823- }
859+ SpiFlashOpResult result;
824860#if PUYA_SUPPORT
825861 if (getFlashChipVendorId () == SPI_FLASH_VENDOR_PUYA) {
826- rc = spi_flash_write_puya (address, const_cast <uint32_t *>(data), size);
862+ result = spi_flash_write_puya (address, const_cast <uint32_t *>(data), size);
827863 }
828864 else
829- #endif // PUYA_SUPPORT
865+ #endif
830866 {
831- rc = spi_flash_write (address, const_cast <uint32_t *>(data), size);
867+ result = spi_flash_write_page_break (address, const_cast <uint32_t *>(data), size);
832868 }
833- return rc == SPI_FLASH_RESULT_OK;
869+ return result == SPI_FLASH_RESULT_OK;
834870}
835871
836872bool EspClass::flashWrite (uint32_t address, const uint8_t *data, size_t size) {
837- if (size == 0 ) {
838- return true ;
839- }
840-
841- size_t sizeLeft = size & ~3 ;
842- size_t currentOffset = 0 ;
843-
844- if (sizeLeft) {
845- if ((uintptr_t )data % 4 != 0 ) {
846- size_t written = flashWriteUnalignedMemory (address, data, size);
847- if (!written) {
848- return false ;
849- }
850- currentOffset += written;
851- sizeLeft -= written;
852- } else {
853- bool pageBreak = ((address % 4 ) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1 ) / FLASH_PAGE_SIZE));
854-
855- if (pageBreak) {
856- while (sizeLeft) {
857- // We cannot cross page boundary, but the write must be 4 byte aligned,
858- // so this is the maximum amount we can write
859- size_t pageBoundary = (FLASH_PAGE_SIZE - ((address + currentOffset) % FLASH_PAGE_SIZE)) & ~3 ;
860-
861- if (sizeLeft > pageBoundary) {
862- // Aligned write up to page boundary
863- if (!flashWrite (address + currentOffset, (uint32_t *)(data + currentOffset), pageBoundary)) {
864- return false ;
865- }
866- currentOffset += pageBoundary;
867- sizeLeft -= pageBoundary;
868- // Cross the page boundary
869- if (!flashWritePageBreak (address + currentOffset, data + currentOffset, 4 )) {
870- return false ;
871- }
872- currentOffset += 4 ;
873- sizeLeft -= 4 ;
874- } else {
875- // We do not cross page boundary
876- if (!flashWrite (address + currentOffset, (uint32_t *)(data + currentOffset), sizeLeft)) {
877- return false ;
878- }
879- currentOffset += sizeLeft;
880- sizeLeft = 0 ;
881- }
882- }
883- } else {
884- // Pointer is properly aligned and write does not cross page boundary,
885- // so use aligned write
886- if (!flashWrite (address, (uint32_t *)data, sizeLeft)) {
887- return false ;
888- }
889- currentOffset = sizeLeft;
890- sizeLeft = 0 ;
891- }
892- }
893- }
894- sizeLeft = size - currentOffset;
895- if (sizeLeft > 0 ) {
896- // Size was not aligned, so we have some bytes left to write, we also need to recheck for
897- // page boundary crossing
898- bool pageBreak = ((address % 4 ) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1 ) / FLASH_PAGE_SIZE));
899-
900- if (pageBreak) {
901- // Cross the page boundary
902- if (!flashWritePageBreak (address + currentOffset, data + currentOffset, sizeLeft)) {
903- return false ;
904- }
905- } else {
906- // Just write partial block
907- flashReplaceBlock (address + currentOffset, data + currentOffset, sizeLeft);
873+ if (data && size) {
874+ if (!isAlignedAddress (address)
875+ || !isAlignedPointer (data)
876+ || !isAlignedSize (size))
877+ {
878+ return flashWriteUnalignedMemory (address, data, size) == size;
908879 }
880+
881+ return flashWrite (address, reinterpret_cast <const uint32_t *>(data), size);
909882 }
910883
911- return true ;
884+ return false ;
912885}
913886
914887bool EspClass::flashRead (uint32_t address, uint8_t *data, size_t size) {
915888 size_t sizeAligned = size & ~3 ;
916889 size_t currentOffset = 0 ;
917890
918891 if ((uintptr_t )data % 4 != 0 ) {
919- uint32_t alignedData[FLASH_PAGE_SIZE / sizeof (uint32_t )] __attribute__ ((aligned (4 )));
892+ constexpr size_t BufferSize { FLASH_PAGE_SIZE / sizeof (uint32_t ) };
893+ alignas (alignof (uint32_t )) uint32_t buf[BufferSize];
920894 size_t sizeLeft = sizeAligned;
921895
922896 while (sizeLeft) {
923- size_t willCopy = std::min (sizeLeft, sizeof (alignedData) );
897+ size_t willCopy = std::min (sizeLeft, BufferSize );
924898 // We read to our aligned buffer and then copy to data
925- if (!flashRead (address + currentOffset, alignedData , willCopy))
899+ if (!flashRead (address + currentOffset, &buf[ 0 ] , willCopy))
926900 {
927901 return false ;
928902 }
929- memcpy (data + currentOffset, alignedData , willCopy);
903+ memcpy (data + currentOffset, &buf[ 0 ] , willCopy);
930904 sizeLeft -= willCopy;
931905 currentOffset += willCopy;
932906 }
933907 } else {
934908 // Pointer is properly aligned, so use aligned read
935- if (!flashRead (address, ( uint32_t *) data, sizeAligned)) {
909+ if (!flashRead (address, reinterpret_cast < uint32_t *>( data) , sizeAligned)) {
936910 return false ;
937911 }
938912 currentOffset = sizeAligned;
939913 }
940914
941915 if (currentOffset < size) {
942916 uint32_t tempData;
943- if (spi_flash_read (address + currentOffset, &tempData, 4 ) != SPI_FLASH_RESULT_OK) {
917+ if (spi_flash_read (address + currentOffset, &tempData, sizeof (tempData) ) != SPI_FLASH_RESULT_OK) {
944918 return false ;
945919 }
946- memcpy (( uint8_t *) data + currentOffset, &tempData, size - currentOffset);
920+ memcpy (data + currentOffset, &tempData, size - currentOffset);
947921 }
948922
949923 return true ;
@@ -973,7 +947,7 @@ String EspClass::getSketchMD5()
973947 md5.begin ();
974948 while ( lengthLeft > 0 ) {
975949 size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
976- if (!flashRead (offset, reinterpret_cast <uint32_t *>(buf.get ()), (readBytes + 3 ) & ~3 )) {
950+ if (!flashRead (offset, reinterpret_cast <uint32_t *>(buf.get ()), (readBytes + 3 ) & ~3 )) {
977951 return emptyString;
978952 }
979953 md5.add (buf.get (), readBytes);
0 commit comments