/*! \file mbl_flash.c \brief Non-secure MBL flash file for GD32W51x WiFi SDK \version 2021-10-30, V1.0.0, firmware for GD32W51x */ /* Copyright (c) 2021, GigaDevice Semiconductor Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "gd32w51x.h" #include "config_gdm32_ntz.h" #include "mbl_trace.h" #include "mbl_flash.h" /*! \brief get flash cache enabled state \param[in] none \param[out] none \retval result of state(1: enabled, or 0: disabled) */ int is_flash_cache_enabled(void) { if (ICACHE_CTL & ICACHE_CTL_EN) return 1; else return 0; } /*! \brief disable flash cache \param[in] none \param[out] none \retval 0 */ int flash_cache_disable(void) { icache_disable(); return 0; } /*! \brief enable flash cache \param[in] none \param[out] none \retval 0 */ int flash_cache_enable(void) { icache_enable(); return 0; } /*! \brief check memory structure is SIP Flash or EXT Flash \param[in] none \param[out] none \retval memory structure(1: FMC mode: SIP Flash, 0: EXT Flash) */ int is_sip_flash(void) { return (OBSTAT_NQSPI() == SET); } /*! \brief get flash offset valid state \param[in] offset: flash offset \param[out] none \retval result of state(1: offset is valid, or 0: offset is invalid) */ int is_valid_flash_offset(uint32_t offset) { if (offset < flash_total_size()) { return 1; } return 0; } /*! \brief get flash addr valid state \param[in] addr: flash address \param[out] none \retval result of state(1: addr is valid, or 0: addr is not valid) */ int is_valid_flash_addr(uint32_t addr) { int bvalid = 0; if ((addr >= FLASH_BASE) && (addr < (FLASH_BASE + flash_total_size()))) { bvalid = 1; } return bvalid; } /*! \brief get flash total size \param[in] none \param[out] none \retval flash total size */ uint32_t flash_total_size(void) { return FLASH_TOTAL_SIZE; } /*! \brief get flash erase size \param[in] none \param[out] none \retval flash erase size. sip flash page size or qspi sector size */ uint32_t flash_erase_size(void) { if (is_sip_flash()) { return FLASH_SIP_PAGE_SIZE; } else { return FLASH_QSPI_SECTOR_SIZE; } } /*! \brief configure no real time decrypt areas for flash \param[in] nd_idx: no decrypt register index \param[in] start_page: start page of no real time decrypt area \param[in] end_page: end page of no real time decrypt area \param[out] none \retval none */ void flash_nodec_config(uint32_t nd_idx, uint32_t start_page, uint32_t end_page) { if (is_sip_flash()) { /* unlock the flash program erase controller */ fmc_unlock(); /* unlock the option byte operation (include SECWM/HDP/WRP/NODEC/OFRG/OFVR) */ ob_unlock(); /* clear pending flags */ fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR); /* set no OTFDEC region for sip flash */ fmc_no_rtdec_config(start_page, end_page, nd_idx); /* lock the option byte operation */ ob_lock(); /* lock the flash program erase controller */ fmc_lock(); } } /*! \brief config flash offset region and value \param[in] none \param[out] none \retval none */ void flash_offset_mapping(void) { fmc_unlock(); ob_unlock(); fmc_offset_region_config(RE_IMG_0_PROT_OFFSET >> 12, (RE_IMG_1_PROT_OFFSET >> 12) - 1); fmc_offset_value_config((RE_IMG_1_PROT_OFFSET - RE_IMG_0_PROT_OFFSET) >> 12); ob_lock(); fmc_lock(); } /*! \brief initialize flash \param[in] none \param[out] none \retval 0 */ int flash_init(void) { if (!is_sip_flash()) { qspi_flash_config(3); } return 0; } /*! \brief read flash \param[in] offset: flash offset \param[out] data: pointer to the buffer store flash read data \param[in] len: length of data read from flash \retval result of read flash(0: read ok, or -1: read error) */ int flash_read(uint32_t offset, void *data, int len) { uint32_t i; uint32_t dst = (uint32_t)data; uint32_t left; if (!is_valid_flash_offset(offset) || data == NULL || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { return -1; } if ((offset & 3) || (dst & 3)) { for (i = 0; i < len; i++) { ((uint8_t *)data)[i] = *(uint8_t *)(FLASH_BASE + offset + i); } } else { left = (len & 3); len -= left; for (i = 0; i < len; i += 4) { *(uint32_t *)(dst + i) = *(uint32_t *)(FLASH_BASE + offset + i); } if (left > 0) memcpy((uint8_t *)(dst + len), (uint8_t *)(FLASH_BASE + offset + len), left); } return 0; } /*! \brief read flash indirectly consider encrypted image, two flash read api will be needed, one is directly read and flash value is decrypted, another is indirect read and no decryption is done. \param[in] offset: flash offset \param[out] data: pointer to the buffer store flash read data \param[in] len: length of data read from flash \retval result of read flash(0: read ok, or -1: read error) */ int flash_indirect_read(uint32_t offset, void *data, int len) { int cache_enabled = 0; int ret; if (is_flash_cache_enabled()) { flash_cache_disable(); cache_enabled = 1; } if (is_sip_flash()) { ret = flash_read(offset, data, len); } else { if (!is_valid_flash_offset(offset) || data == NULL || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { ret = -1; goto Exit; } __disable_irq(); ret = qspi_flash_read(FLASH_START_QSPI + offset, data, len); __enable_irq(); } Exit: if (cache_enabled) flash_cache_enable(); return ret; } /*! \brief read flash while verify image during OTA \param[in] offset: flash offset \param[out] data: pointer to the buffer store flash read data \param[in] len: length of data read from flash \retval result of read flash(0: read ok, or -1: read error) */ int flash_verify_read(uint32_t offset, void *data, int len) { int cache_enabled = 0; int ret; uint32_t fmc_ofvr_temp = FMC_OFVR; if (is_flash_cache_enabled()) { flash_cache_disable(); cache_enabled = 1; } if (is_sip_flash()) { __disable_irq(); //disable all irqs here, cause it will change the FMC_OFVR value fmc_unlock(); ob_unlock(); fmc_offset_value_config(0);// reset FMC_OFVR to 0 ob_lock(); fmc_lock(); ret = flash_read(offset, data, len); fmc_unlock(); ob_unlock(); fmc_offset_value_config(fmc_ofvr_temp);// recovery FMC_OFVR value ob_lock(); fmc_lock(); __enable_irq();// enable irq here } else { if (!is_valid_flash_offset(offset) || data == NULL || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { ret = -1; goto Exit; } __disable_irq(); ret = qspi_flash_read(FLASH_START_QSPI + offset, data, len); __enable_irq(); } Exit: if (cache_enabled) flash_cache_enable(); return ret; } /*! \brief write flash \param[in] offset: flash offset \param[in] data: pointer to the data write to flash \param[in] len: length of data write to flash \param[out] none \retval result of write flash(0: write ok, or -1: write error) */ int flash_write(uint32_t offset, const void *data, int len) { uint8_t *data_u8 = (uint8_t *)data; uint32_t base_addr; if (!is_valid_flash_offset(offset) || data == NULL || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { return -1; } if (is_sip_flash()) { uint32_t offset_align, val32; int vb, act_len, i; uint8_t val[4] = {0xFF, 0xFF, 0xFF, 0xFF}; base_addr = FLASH_BASE; /* unlock the flash program erase controller */ fmc_unlock(); /* clear pending flags */ fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR); offset_align = (offset & ~0x3); /* if offset is not 4-byte alignment */ vb = (offset & 0x3); if (vb > 0) { act_len = ((4 - vb) > len) ? len : (4 - vb); for (i = 0; i < act_len; i++) { val[vb + i] = *(data_u8 + i); } fmc_word_program((base_addr + offset_align), *(uint32_t *)val); offset_align += 4; data_u8 += act_len; len -= act_len; } /* word program */ while (len >= 4) { fmc_word_program((base_addr + offset_align), *(uint32_t *)data_u8); offset_align += 4; data_u8 += 4; len -= 4; } /* if len is not 4-byte alignment */ val32 = 0xFFFFFFFF; if (len > 0) { while (len-- > 0) { val32 = (val32 << 8); val32 |= *(data_u8 + len); } fmc_word_program((base_addr + offset_align), val32); } /* lock the flash program erase controller */ fmc_lock(); } else { uint32_t page_offset; uint32_t size_to_program; base_addr = FLASH_START_QSPI; page_offset = (offset & (FLASH_QSPI_PAGE_SIZE - 1)); if (page_offset != 0) { size_to_program = (len > FLASH_QSPI_PAGE_SIZE - page_offset) ? (FLASH_QSPI_PAGE_SIZE - page_offset) : len; __disable_irq(); qspi_flash_page_program((base_addr + offset), data_u8, size_to_program); __enable_irq(); offset += size_to_program; data_u8 += size_to_program; len -= size_to_program; } while (len > 0) { size_to_program = (len > FLASH_QSPI_PAGE_SIZE) ? FLASH_QSPI_PAGE_SIZE : len; __disable_irq(); qspi_flash_page_program((base_addr + offset), data_u8, size_to_program); __enable_irq(); offset += size_to_program; data_u8 += size_to_program; len -= size_to_program; } } return 0; } /*! \brief write flash fast \param[in] offset: flash offset \param[in] data: pointer to the data write to flash \param[in] len: length of data write to flash \param[out] none \retval result of write flash(0: write ok, or -1: write error) */ int flash_fast_write(uint32_t offset, const void *data, int len) { uint8_t *data_u8 = (uint8_t *)data; uint32_t base_addr; int ret = 0; if (!is_valid_flash_offset(offset) || data == NULL || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { return -1; } if (is_sip_flash()) { uint8_t r, rr; if (len <= 4) return flash_write(offset, data, len); r = 4 - (offset & 0x3); rr = (offset + len) & 0x3; r = r == 4 ? 0 : r; if (r) { ret = flash_write(offset, data, r); if (ret) return ret; } /* unlock the flash program erase controller */ fmc_unlock(); /* clear pending flags */ fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR); /* prevent interrupt handler from reading flash, it will disrupt the flash continuous programming pipeline */ __disable_irq(); ret = fmc_continuous_program(FLASH_BASE + offset + r, (uint32_t *)((uint8_t *)data + r), len - r - rr); __enable_irq(); /* lock the flash program erase controller */ fmc_lock(); if (rr) { ret = flash_write(offset + len - rr, ((uint8_t *)data + len - rr), rr); if (ret) return ret; } } else { uint32_t page_offset; uint32_t size_to_program; base_addr = FLASH_START_QSPI; page_offset = (offset & (FLASH_QSPI_PAGE_SIZE - 1)); if (page_offset != 0) { size_to_program = (len > FLASH_QSPI_PAGE_SIZE - page_offset) ? (FLASH_QSPI_PAGE_SIZE - page_offset) : len; __disable_irq(); qspi_flash_page_program((base_addr + offset), data_u8, size_to_program); __enable_irq(); offset += size_to_program; data_u8 += size_to_program; len -= size_to_program; } while (len > 0) { size_to_program = (len > FLASH_QSPI_PAGE_SIZE) ? FLASH_QSPI_PAGE_SIZE : len; __disable_irq(); qspi_flash_page_program((base_addr + offset), data_u8, size_to_program); __enable_irq(); offset += size_to_program; data_u8 += size_to_program; len -= size_to_program; } } return ret; } /*! \brief erase flash \param[in] offset: flash offset \param[in] len: flash erase length \param[out] none \retval result of erase flash(0: erase ok, or -1: erase error) */ int flash_erase(uint32_t offset, int len) { int ret; uint32_t erase_sz = flash_erase_size(); uint32_t page_start, sector_start; if (!is_valid_flash_offset(offset) || len <= 0 || !is_valid_flash_offset(offset + len - 1)) { return -1; } if (is_sip_flash()) { /* Get page start */ page_start = FLASH_BASE + (offset & (~(erase_sz - 1))); /* unlock the flash program erase controller */ fmc_unlock(); /* clear pending flags */ fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_OBERR | FMC_FLAG_WPERR); while (len > 0) { /* erase page */ ret = fmc_page_erase(page_start); if (ret != FMC_READY) return -2; page_start += erase_sz; len -= erase_sz; } /* lock the flash program erase controller */ fmc_lock(); } else { sector_start = FLASH_START_QSPI + (offset & (~(erase_sz - 1))); while (len > 0) { __disable_irq(); qspi_flash_sector_erase(sector_start); __enable_irq(); sector_start += erase_sz; len -= erase_sz; } } return 0; }