123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- /*********************
- * flash_storage.cpp *
- *********************/
-
- /****************************************************************************
- * Written By Mark Pelletier 2017 - Aleph Objects, Inc. *
- * Written By Marcio Teixeira 2018 - Aleph Objects, Inc. *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * To view a copy of the GNU General Public License, go to the following *
- * location: <https://www.gnu.org/licenses/>. *
- ****************************************************************************/
-
- #include "../compat.h"
-
- #if ENABLED(TOUCH_UI_FTDI_EVE)
-
- #include "../ftdi_eve_lib/ftdi_eve_lib.h"
-
- #include "media_file_reader.h"
- #include "flash_storage.h"
-
- // The following must be changed whenever the layout of the flash
- // data is changed in a manner that would render the data invalid.
-
- constexpr uint32_t flash_eeprom_version = 1;
-
- /* SPI Flash Memory Map:
- *
- * The following offsets and sizes are specified in 4k erase units:
- *
- * Page Size Description
- * 0 16 DATA STORAGE AREA
- * 16 1 VERSIONING DATA
- * 17 inf MEDIA STORAGE AREA
- */
-
- #define DATA_STORAGE_SIZE_64K
-
- using namespace FTDI::SPI;
- using namespace FTDI::SPI::most_significant_byte_first;
-
- bool UIFlashStorage::is_present = false;
-
- #ifdef SPI_FLASH_SS
- /************************** SPI Flash Chip Interface **************************/
-
- void SPIFlash::wait_while_busy() {
- uint8_t status;
- safe_delay(1);
- do {
- spi_flash_select();
- spi_write_8(READ_STATUS_1);
- status = spi_read_8();
- spi_flash_deselect();
- safe_delay(1);
- } while (status & 1);
- }
-
- void SPIFlash::erase_sector_4k(uint32_t addr) {
- spi_flash_select();
- spi_write_8(WRITE_ENABLE);
- spi_flash_deselect();
-
- spi_flash_select();
- spi_write_8(ERASE_4K);
- spi_write_24(addr);
- spi_flash_deselect();
-
- wait_while_busy();
- }
-
- void SPIFlash::erase_sector_64k(uint32_t addr) {
- spi_flash_select();
- spi_write_8(WRITE_ENABLE);
- spi_flash_deselect();
-
- spi_flash_select();
- spi_write_8(ERASE_64K);
- spi_write_24(addr);
- spi_flash_deselect();
-
- wait_while_busy();
- }
-
- void SPIFlash::spi_write_begin(uint32_t addr) {
- spi_flash_select();
- spi_write_8(WRITE_ENABLE);
- spi_flash_deselect();
-
- spi_flash_select();
- spi_write_8(PAGE_PROGRAM);
- spi_write_24(addr);
- }
-
- void SPIFlash::spi_write_end() {
- spi_flash_deselect();
- wait_while_busy();
- }
-
- void SPIFlash::spi_read_begin(uint32_t addr) {
- spi_flash_select();
- spi_write_8(READ_DATA);
- spi_write_24(addr);
- }
-
- void SPIFlash::spi_read_end() {
- spi_flash_deselect();
- }
-
- void SPIFlash::erase_chip() {
- spi_flash_select();
- spi_write_8(WRITE_ENABLE);
- spi_flash_deselect();
-
- spi_flash_select();
- spi_write_8(ERASE_CHIP);
- spi_flash_deselect();
- wait_while_busy();
- }
-
- void SPIFlash::read_jedec_id(uint8_t &manufacturer_id, uint8_t &device_type, uint8_t &capacity) {
- spi_flash_select();
- spi_write_8(READ_JEDEC_ID);
- manufacturer_id = spi_recv();
- device_type = spi_recv();
- capacity = spi_recv();
- spi_flash_deselect ();
- }
-
- /* This function writes "size" bytes from "data" starting at addr, while properly
- * taking into account the special case of writing across a 256 byte page boundary.
- * Returns the addr directly after the write.
- */
- uint32_t SPIFlash::write(uint32_t addr, const void *_data, size_t size) {
- const uint8_t *data = (const uint8_t*) _data;
- while (size) {
- const uint32_t page_start = addr & 0xFFFF00ul;
- const uint32_t page_end = page_start + 256;
- const uint32_t write_size = min(page_end - addr, size);
- spi_write_begin(addr);
- spi_write_bulk<ram_write>(data, write_size);
- spi_write_end();
- addr += write_size;
- size -= write_size;
- data += write_size;
- }
- return addr;
- }
-
- uint32_t SPIFlash::read(uint32_t addr, void *data, size_t size) {
- spi_read_begin(addr);
- spi_read_bulk(data, size);
- spi_read_end();
- return addr + size;
- }
-
- /********************************** UTILITY ROUTINES *********************************/
-
- bool UIFlashStorage::check_known_device() {
- uint8_t manufacturer_id, device_type, capacity;
- read_jedec_id(manufacturer_id, device_type, capacity);
-
- const bool is_known =
- ((manufacturer_id == 0xEF) && (device_type == 0x40) && (capacity == 0x15)) || // unknown
- ((manufacturer_id == 0x01) && (device_type == 0x40) && (capacity == 0x15)) || // Cypress S25FL116K
- ((manufacturer_id == 0xEF) && (device_type == 0x14) && (capacity == 0x15)) || // Winbond W25Q16JV
- ((manufacturer_id == 0x1F) && (device_type == 0x86) && (capacity == 0x01)) ; // Adesto AT255F161
-
- if (!is_known) {
- SERIAL_ECHO_MSG("Unable to locate supported SPI Flash Memory.");
- SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(" Manufacturer ID, got: ", manufacturer_id);
- SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(" Device Type , got: ", device_type);
- SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(" Capacity , got: ", capacity);
- }
-
- return is_known;
- }
-
- void UIFlashStorage::initialize() {
- for (uint8_t i = 0; i < 10; i++) {
- if (check_known_device()) {
- is_present = true;
- break;
- }
- safe_delay(1000);
- }
- }
-
- /**************************** DATA STORAGE AREA (first 4K or 64k) ********************/
-
- #ifdef DATA_STORAGE_SIZE_64K
- constexpr uint32_t data_storage_area_size = 64 * 1024; // Large erase unit
- #else
- constexpr uint32_t data_storage_area_size = 4 * 1024; // Small erase unit
- #endif
-
- /* In order to provide some degree of wear leveling, each data write to the
- * SPI Flash chip is appended to data that was already written before, until
- * the data storage area is completely filled. New data is written preceeded
- * with a 32-bit delimiter 'LULZ', so that we can distinguish written and
- * unwritten data:
- *
- * 'LULZ' <--- 1st record delimiter
- * <data_byte>
- * <data_byte>
- * <data_byte>
- * 'LULZ' <--- 2nd record delimiter
- * <data_byte>
- * <data_byte>
- * <data_byte>
- * ...
- * 'LULZ' <--- Last record delimiter
- * <data_byte>
- * <data_byte>
- * <data_byte>
- * 0xFF <--- Start of free space
- * 0xFF
- * ...
- *
- * This function walks down the data storage area, verifying that the
- * delimiters are either 'LULZ' or 0xFFFFFFFF. In the case that an invalid
- * delimiter is found, this function returns -1, indicating that the Flash
- * data is invalid (this will happen if the block_size changed with respect
- * to earlier firmware). Otherwise, it returns the offset of the last
- * valid delimiter 'LULZ', indicating the most recently written data.
- */
- int32_t UIFlashStorage::get_config_read_offset(uint32_t block_size) {
- uint16_t stride = 4 + block_size;
- int32_t read_offset = -1;
-
- for (uint32_t offset = 0; offset < (data_storage_area_size - stride); offset += stride) {
- uint32_t delim;
- spi_read_begin(offset);
- spi_read_bulk (&delim, sizeof(delim));
- spi_read_end();
- switch (delim) {
- case 0xFFFFFFFFul: return read_offset;
- case delimiter: read_offset = offset; break;
- default:
- SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("Invalid delimiter in Flash: ", delim);
- return -1;
- }
- }
- SERIAL_ECHO_MSG("No LULZ delimiter found.");
- return -1;
- }
-
- /* This function returns the offset at which new data should be
- * appended, or -1 if the Flash needs to be erased */
- int32_t UIFlashStorage::get_config_write_offset(uint32_t block_size) {
- int32_t read_offset = get_config_read_offset(block_size);
- if (read_offset == -1) return -1; // The SPI flash is invalid
-
- int32_t write_offset = read_offset + 4 + block_size;
- if ((write_offset + 4 + block_size) > data_storage_area_size) {
- SERIAL_ECHO_MSG("Not enough free space in Flash.");
- return -1; // Not enough free space
- }
- return write_offset;
- }
-
- bool UIFlashStorage::verify_config_data(const void *data, size_t size) {
- if (!is_present) return false;
-
- int32_t read_addr = get_config_read_offset(size);
- if (read_addr == -1) return false;
-
- uint32_t delim;
- spi_read_begin(read_addr);
- spi_read_bulk (&delim, sizeof(delim));
- bool ok = spi_verify_bulk(data,size);
- spi_read_end();
- return ok && delim == delimiter;
- }
-
- bool UIFlashStorage::read_config_data(void *data, size_t size) {
- if (!is_present) return false;
-
- int32_t read_addr = get_config_read_offset(size);
- if (read_addr == -1) return false;
-
- uint32_t delim;
- spi_read_begin(read_addr);
- spi_read_bulk (&delim, sizeof(delim));
- spi_read_bulk (data, size);
- spi_read_end();
- return delim == delimiter;
- }
-
- void UIFlashStorage::write_config_data(const void *data, size_t size) {
- if (!is_present) {
- SERIAL_ECHO_MSG("SPI Flash chip not present. Not saving UI settings.");
- return;
- }
-
- // Since Flash storage has a limited number of write cycles,
- // make sure that the data is different before rewriting.
-
- if (verify_config_data(data, size)) {
- SERIAL_ECHO_MSG("UI settings already written, skipping write.");
- return;
- }
-
- int16_t write_addr = get_config_write_offset(size);
- if (write_addr == -1) {
- SERIAL_ECHO_START();
- SERIAL_ECHOPGM("Erasing UI settings from SPI Flash... ");
- #ifdef DATA_STORAGE_SIZE_64K
- erase_sector_64k(0);
- #else
- erase_sector_4k(0);
- #endif
- write_addr = 0;
- SERIAL_ECHOLNPGM("DONE");
- }
-
- SERIAL_ECHO_START();
- SERIAL_ECHOPAIR("Writing UI settings to SPI Flash (offset ", write_addr);
- SERIAL_ECHOPGM(")...");
-
- const uint32_t delim = delimiter;
- write_addr = write(write_addr, &delim, sizeof(delim));
- write_addr = write(write_addr, data, size);
-
- SERIAL_ECHOLNPGM("DONE");
- }
-
- /************************** VERSIONING INFO AREA ************************/
-
- /* The version info area follows the data storage area. If the version
- * is incorrect, the data on the chip is invalid and format_flash should
- * be called.
- */
-
- typedef struct {
- uint32_t magic;
- uint32_t version;
- } flash_version_info;
-
- constexpr uint32_t version_info_addr = data_storage_area_size;
- constexpr uint32_t version_info_size = 4 * 1024; // Small erase unit
-
- bool UIFlashStorage::is_valid() {
- flash_version_info info;
-
- spi_read_begin(version_info_addr);
- spi_read_bulk (&info, sizeof(flash_version_info));
- spi_read_end();
-
- return info.magic == delimiter && info.version == flash_eeprom_version;
- }
-
- void UIFlashStorage::write_version_info() {
- flash_version_info info;
-
- info.magic = delimiter;
- info.version = flash_eeprom_version;
-
- spi_write_begin(version_info_addr);
- spi_write_bulk<ram_write>(&info, sizeof(flash_version_info));
- spi_write_end();
- }
-
- /**************************** MEDIA STORAGE AREA *****************************/
-
- /* The media storage area follows the versioning info area. It consists
- * of a file index followed by the data for one or more media files.
- *
- * The file index consists of an array of 32-bit file sizes. If a file
- * is not present, the file's size will be set to 0xFFFFFFFF
- */
-
- constexpr uint32_t media_storage_addr = version_info_addr + version_info_size;
- constexpr uint8_t media_storage_slots = 4;
-
- void UIFlashStorage::format_flash() {
- SERIAL_ECHO_START(); SERIAL_ECHOPGM("Erasing SPI Flash...");
- SPIFlash::erase_chip();
- SERIAL_ECHOLNPGM("DONE");
-
- write_version_info();
- }
-
- uint32_t UIFlashStorage::get_media_file_start(uint8_t slot) {
- uint32_t addr = media_storage_addr + sizeof(uint32_t) * media_storage_slots;
- spi_read_begin(media_storage_addr);
- for (uint8_t i = 0; i < slot; i++)
- addr += spi_read_32();
- spi_read_end();
- return addr;
- }
-
- void UIFlashStorage::set_media_file_size(uint8_t slot, uint32_t size) {
- spi_write_begin(media_storage_addr + sizeof(uint32_t) * slot);
- spi_write_32(size);
- spi_write_end();
- }
-
- uint32_t UIFlashStorage::get_media_file_size(uint8_t slot) {
- spi_read_begin(media_storage_addr + sizeof(uint32_t) * slot);
- uint32_t size = spi_read_32();
- spi_read_end();
- return size;
- }
-
- /* Writes a media file from the SD card/USB flash drive into a slot on the SPI Flash. Media
- * files must be written sequentially following by a chip erase and it is not possible to
- * overwrite files. */
- UIFlashStorage::error_t UIFlashStorage::write_media_file(progmem_str filename, uint8_t slot) {
- #if ENABLED(SDSUPPORT)
- uint32_t addr;
- uint8_t buff[write_page_size];
-
- strcpy_P( (char*) buff, (const char*) filename);
-
- MediaFileReader reader;
- if (!reader.open((char*) buff)) {
- SERIAL_ECHO_MSG("Unable to find media file");
- return FILE_NOT_FOUND;
- }
-
- if (get_media_file_size(slot) != 0xFFFFFFFFUL) {
- SERIAL_ECHO_MSG("Media file already exists");
- return WOULD_OVERWRITE;
- }
-
- SERIAL_ECHO_START(); SERIAL_ECHOPGM("Writing SPI Flash...");
-
- set_media_file_size(slot, reader.size());
- addr = get_media_file_start(slot);
-
- // Write out the file itself
- for (;;) {
- const int16_t nBytes = reader.read(buff, write_page_size);
- if (nBytes == -1) {
- SERIAL_ECHOLNPGM("Failed to read from file");
- return READ_ERROR;
- }
-
- addr = write(addr, buff, nBytes);
- if (nBytes != write_page_size) break;
-
- TERN_(EXTENSIBLE_UI, ExtUI::yield());
- }
-
- SERIAL_ECHOLNPGM("DONE");
-
- SERIAL_ECHO_START(); SERIAL_ECHOPGM("Verifying SPI Flash...");
-
- bool verifyOk = true;
-
- // Verify the file index
-
- if (get_media_file_start(slot+1) != (get_media_file_start(slot) + reader.size())) {
- SERIAL_ECHOLNPGM("File index verification failed. ");
- verifyOk = false;
- }
-
- // Verify the file itself
- addr = get_media_file_start(slot);
- reader.rewind();
-
- while (verifyOk) {
- const int16_t nBytes = reader.read(buff, write_page_size);
- if (nBytes == -1) {
- SERIAL_ECHOPGM("Failed to read from file");
- verifyOk = false;
- break;
- }
-
- spi_read_begin(addr);
- if (!spi_verify_bulk(buff, nBytes)) {
- verifyOk = false;
- spi_read_end();
- break;
- }
- spi_read_end();
-
- addr += nBytes;
- if (nBytes != write_page_size) break;
- TERN_(EXTENSIBLE_UI, ExtUI::yield());
- };
-
- if (verifyOk) {
- SERIAL_ECHOLNPGM("DONE");
- return SUCCESS;
- }
- else {
- SERIAL_ECHOLNPGM("FAIL");
- return VERIFY_ERROR;
- }
- #else
- return VERIFY_ERROR;
- #endif // SDSUPPORT
- }
-
- bool UIFlashStorage::BootMediaReader::isAvailable(uint32_t slot) {
- if (!is_present) return false;
-
- bytes_remaining = get_media_file_size(slot);
- if (bytes_remaining != 0xFFFFFFFFUL) {
- SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("Boot media file size:", bytes_remaining);
- addr = get_media_file_start(slot);
- return true;
- }
- return false;
- }
-
- int16_t UIFlashStorage::BootMediaReader::read(void *data, const size_t size) {
- if (bytes_remaining == 0xFFFFFFFFUL) return -1;
-
- if (size > bytes_remaining)
- return read(data, bytes_remaining);
-
- if (size > 0) {
- spi_read_begin(addr);
- spi_read_bulk(data, size);
- spi_read_end();
- addr += size;
- bytes_remaining -= size;
- }
-
- return size;
- }
-
- int16_t UIFlashStorage::BootMediaReader::read(void *obj, void *data, const size_t size) {
- return reinterpret_cast<UIFlashStorage::BootMediaReader*>(obj)->read(data, size);
- }
-
- #else
- void UIFlashStorage::initialize() {}
- bool UIFlashStorage::is_valid() {return true;}
- void UIFlashStorage::write_config_data(const void *, size_t) {}
- bool UIFlashStorage::verify_config_data(const void *, size_t) {return false;}
- bool UIFlashStorage::read_config_data(void *, size_t ) {return false;}
- UIFlashStorage::error_t UIFlashStorage::write_media_file(progmem_str, uint8_t) {return FILE_NOT_FOUND;}
- void UIFlashStorage::format_flash() {}
-
- bool UIFlashStorage::BootMediaReader::isAvailable(uint32_t) {return false;}
- int16_t UIFlashStorage::BootMediaReader::read(void *, const size_t) {return -1;}
- int16_t UIFlashStorage::BootMediaReader::read(void *, void *, const size_t) {return -1;}
- #endif // SPI_FLASH_SS
- #endif // TOUCH_UI_FTDI_EVE
|