Skip to content

Commit da5f944

Browse files
mcsprhasenradball
authored andcommitted
Correctly handle unaligned address in EspClass::flashWrite u8 overload (esp8266#8605)
Separate page handling logic and the actual writing. Make sure we place both unaligned src and dest into a buffer. Fixes edge-case introduced for SPIFFS that exclusively works through unaligned flash write function. This copies the behaviour of official RTOS port, but does not change the original spi_flash_write.
1 parent c42189a commit da5f944

File tree

3 files changed

+154
-197
lines changed

3 files changed

+154
-197
lines changed

cores/esp8266/Esp.cpp

+140-166
Original file line numberDiff line numberDiff line change
@@ -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
677716
static 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+
759788
size_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

817858
bool 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

836872
bool 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

914887
bool 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

Comments
 (0)