text: /*
   * This file is part of the EasyFlash Library.
   *
   * Copyright (c) 2019, Armink, <armink.ztl@gmail.com>
   *
   * Permission is hereby granted, free of charge, to any person obtaining
   * a copy of this software and associated documentation files (the
   * 'Software'), to deal in the Software without restriction, including
   * without limitation the rights to use, copy, modify, merge, publish,
   * distribute, sublicense, and/or sell copies of the Software, and to
   * permit persons to whom the Software is furnished to do so, subject to
   * the following conditions:
   *
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   *
   * Function: Environment variables operating interface. This is the Next Generation version.
   * Created on: 2019-02-02
   */
  
  #include <string.h>
  #include <easyflash.h>
  
  #if defined(EF_USING_ENV) && !defined(EF_ENV_USING_LEGACY_MODE)
  
  #ifndef EF_WRITE_GRAN
  #error "Please configure flash write granularity (in ef_cfg.h)"
  #endif
  
  #if EF_WRITE_GRAN != 1 && EF_WRITE_GRAN != 8 && EF_WRITE_GRAN != 32 && EF_WRITE_GRAN != 64
  #error "the write gran can be only setting as 1, 8, 32 and 64"
  #endif
  
  /* the ENV max name length must less then it */
  #ifndef EF_ENV_NAME_MAX
  #define EF_ENV_NAME_MAX                          32
  #endif
  
  /* magic word(`E`, `F`, `4`, `0`) */
  #define SECTOR_MAGIC_WORD                        0x30344645
  /* magic word(`K`, `V`, `4`, `0`) */
  #define ENV_MAGIC_WORD                           0x3034564B
  
  /* the using status sector table length */
  #ifndef USING_SECTOR_TABLE_LEN
  #define USING_SECTOR_TABLE_LEN                   4
  #endif
  
  /* the string ENV value buffer size for legacy ef_get_env() function */
  #ifndef EF_STR_ENV_VALUE_MAX_SIZE
  #define EF_STR_ENV_VALUE_MAX_SIZE                128
  #endif
  
  /* the sector remain threshold before full status */
  #ifndef EF_SEC_REMAIN_THRESHOLD
  #define EF_SEC_REMAIN_THRESHOLD                  (ENV_HDR_DATA_SIZE + EF_ENV_NAME_MAX)
  #endif
  
  /* the total remain empty sector threshold before GC */
  #ifndef EF_GC_EMPTY_SEC_THRESHOLD
  #define EF_GC_EMPTY_SEC_THRESHOLD                1
  #endif
  
  /* the ENV cache table size, it will improve ENV search speed when using cache */
  #ifndef EF_ENV_CACHE_TABLE_SIZE
  #define EF_ENV_CACHE_TABLE_SIZE                  16
  #endif
  
  /* the sector cache table size, it will improve ENV save speed when using cache */
  #ifndef EF_SECTOR_CACHE_TABLE_SIZE
  #define EF_SECTOR_CACHE_TABLE_SIZE               4
  #endif
  
  #if EF_ENV_CACHE_TABLE_SIZE > 0xFFFF
  #error "The ENV cache table size must less than 0xFFFF"
  #endif
  
  #if (EF_ENV_CACHE_TABLE_SIZE > 0) && (EF_SECTOR_CACHE_TABLE_SIZE > 0)
  #define EF_ENV_USING_CACHE
  #endif
  
  /* the sector is not combined value */
  #define SECTOR_NOT_COMBINED                      0xFFFFFFFF
  /* the next address is get failed */
  #define FAILED_ADDR                              0xFFFFFFFF
  
  /* Return the most contiguous size aligned at specified width. RT_ALIGN(13, 4)
   * would return 16.
   */
  #define EF_ALIGN(size, align)                    (((size) + (align) - 1) & ~((align) - 1))
  /* align by write granularity */
  #define EF_WG_ALIGN(size)                        (EF_ALIGN(size, (EF_WRITE_GRAN + 7)/8))
  /**
   * Return the down number of aligned at specified width. RT_ALIGN_DOWN(13, 4)
   * would return 12.
   */
  #define EF_ALIGN_DOWN(size, align)               ((size) & ~((align) - 1))
  /* align down by write granularity */
  #define EF_WG_ALIGN_DOWN(size)                   (EF_ALIGN_DOWN(size, (EF_WRITE_GRAN + 7)/8))
  
  #if (EF_WRITE_GRAN == 1)
  #define STATUS_TABLE_SIZE(status_number)         ((status_number * EF_WRITE_GRAN + 7)/8)
  #else
  #define STATUS_TABLE_SIZE(status_number)         (((status_number - 1) * EF_WRITE_GRAN + 7)/8)
  #endif
  
  #define STORE_STATUS_TABLE_SIZE                  STATUS_TABLE_SIZE(SECTOR_STORE_STATUS_NUM)
  #define DIRTY_STATUS_TABLE_SIZE                  STATUS_TABLE_SIZE(SECTOR_DIRTY_STATUS_NUM)
  #define ENV_STATUS_TABLE_SIZE                    STATUS_TABLE_SIZE(ENV_STATUS_NUM)
  
  #define SECTOR_SIZE                              EF_ERASE_MIN_SIZE
  #define SECTOR_NUM                               (ENV_AREA_SIZE / (EF_ERASE_MIN_SIZE))
  
  #if (SECTOR_NUM < 2)
  #error "The sector number must lager then or equal to 2"
  #endif
  
  #if (EF_GC_EMPTY_SEC_THRESHOLD == 0 || EF_GC_EMPTY_SEC_THRESHOLD >= SECTOR_NUM)
  #error "There is at least one empty sector for GC."
  #endif
  
  #define SECTOR_HDR_DATA_SIZE                     (EF_WG_ALIGN(sizeof(struct sector_hdr_data)))
  #define SECTOR_DIRTY_OFFSET                      ((unsigned long)(&((struct sector_hdr_data *)0)->status_table.dirty))
  #define ENV_HDR_DATA_SIZE                        (EF_WG_ALIGN(sizeof(struct env_hdr_data)))
  #define ENV_MAGIC_OFFSET                         ((unsigned long)(&((struct env_hdr_data *)0)->magic))
  #define ENV_LEN_OFFSET                           ((unsigned long)(&((struct env_hdr_data *)0)->len))
  #define ENV_NAME_LEN_OFFSET                      ((unsigned long)(&((struct env_hdr_data *)0)->name_len))
  
  #define VER_NUM_ENV_NAME                         "__ver_num__"
  
  enum sector_store_status {
      SECTOR_STORE_UNUSED,
      SECTOR_STORE_EMPTY,
      SECTOR_STORE_USING,
      SECTOR_STORE_FULL,
      SECTOR_STORE_STATUS_NUM,
  };
  typedef enum sector_store_status sector_store_status_t;
  
  enum sector_dirty_status {
      SECTOR_DIRTY_UNUSED,
      SECTOR_DIRTY_FALSE,
      SECTOR_DIRTY_TRUE,
      SECTOR_DIRTY_GC,
      SECTOR_DIRTY_STATUS_NUM,
  };
  typedef enum sector_dirty_status sector_dirty_status_t;
  
  enum env_status {
      ENV_UNUSED,
      ENV_PRE_WRITE,
      ENV_WRITE,
      ENV_PRE_DELETE,
      ENV_DELETED,
      ENV_ERR_HDR,
      ENV_STATUS_NUM,
  };
  typedef enum env_status env_status_t;
  
  __attribute__((aligned(EF_WRITE_GRAN/8)))
  struct sector_hdr_data {
      struct {
          uint8_t store[STORE_STATUS_TABLE_SIZE];  /**< sector store status @see sector_store_status_t */
          uint8_t dirty[DIRTY_STATUS_TABLE_SIZE];  /**< sector dirty status @see sector_dirty_status_t */
      } status_table;
      uint32_t magic;                              /**< magic word(`E`, `F`, `4`, `0`) */
      uint32_t combined;                           /**< the combined next sector number, 0xFFFFFFFF: not combined */
      uint32_t reserved;
  };
  typedef struct sector_hdr_data *sector_hdr_data_t;
  
  struct sector_meta_data {
      bool check_ok;                               /**< sector header check is OK */
      struct {
          sector_store_status_t store;             /**< sector store status @see sector_store_status_t */
          sector_dirty_status_t dirty;             /**< sector dirty status @see sector_dirty_status_t */
      } status;
      uint32_t addr;                               /**< sector start address */
      uint32_t magic;                              /**< magic word(`E`, `F`, `4`, `0`) */
      uint32_t combined;                           /**< the combined next sector number, 0xFFFFFFFF: not combined */
      size_t remain;                               /**< remain size */
      uint32_t empty_env;                          /**< the next empty ENV node start address */
  };
  typedef struct sector_meta_data *sector_meta_data_t;
  
  struct env_hdr_data {
      uint8_t status_table[ENV_STATUS_TABLE_SIZE]; /**< ENV node status, @see node_status_t */
      uint32_t magic;                              /**< magic word(`K`, `V`, `4`, `0`) */
      uint32_t len;                                /**< ENV node total length (header + name + value), must align by EF_WRITE_GRAN */
      uint32_t crc32;                              /**< ENV node crc32(name_len + data_len + name + value) */
      uint8_t name_len;                            /**< name length */
      uint32_t value_len;                          /**< value length */
  };
  typedef struct env_hdr_data *env_hdr_data_t;
  
  struct env_meta_data {
      env_status_t status;                         /**< ENV node status, @see node_status_t */
      bool crc_is_ok;                              /**< ENV node CRC32 check is OK */
      uint8_t name_len;                            /**< name length */
      uint32_t magic;                              /**< magic word(`K`, `V`, `4`, `0`) */
      uint32_t len;                                /**< ENV node total length (header + name + value), must align by EF_WRITE_GRAN */
      uint32_t value_len;                          /**< value length */
      char name[EF_ENV_NAME_MAX];                  /**< name */
      struct {
          uint32_t start;                          /**< ENV node start address */
          uint32_t value;                          /**< value start address */
      } addr;
  };
  typedef struct env_meta_data *env_meta_data_t;
  
  struct env_cache_node {
      uint16_t name_crc;                           /**< ENV name's CRC32 low 16bit value */
      uint16_t active;                             /**< ENV node access active degree */
      uint32_t addr;                               /**< ENV node address */
  };
  typedef struct env_cache_node *env_cache_node_t;
  
  struct sector_cache_node {
      uint32_t addr;                               /**< sector start address */
      uint32_t empty_addr;                         /**< sector empty address */
  };
  typedef struct sector_cache_node *sector_cache_node_t;
  
  static void gc_collect(void);
  
  /* ENV start address in flash */
  static uint32_t env_start_addr = 0;
  /* default ENV set, must be initialized by user */
  static ef_env const *default_env_set;
  /* default ENV set size, must be initialized by user */
  static size_t default_env_set_size = 0;
  /* initialize OK flag */
  static bool init_ok = false;
  /* request a GC check */
  static bool gc_request = false;
  /* is in recovery check status when first reboot */
  static bool in_recovery_check = false;
  
  #ifdef EF_ENV_USING_CACHE
  /* ENV cache table */
  struct env_cache_node env_cache_table[EF_ENV_CACHE_TABLE_SIZE] = { 0 };
  /* sector cache table, it caching the sector info which status is current using */
  struct sector_cache_node sector_cache_table[EF_SECTOR_CACHE_TABLE_SIZE] = { 0 };
  #endif /* EF_ENV_USING_CACHE */
  
  static size_t set_status(uint8_t status_table[], size_t status_num, size_t status_index)
  {
      size_t byte_index = ~0UL;
      /*
       * | write garn |       status0       |       status1       |      status2         |
       * ---------------------------------------------------------------------------------
       * |    1bit    | 0xFF                | 0x7F                |  0x3F                |
       * |    8bit    | 0xFFFF              | 0x00FF              |  0x0000              |
       * |   32bit    | 0xFFFFFFFF FFFFFFFF | 0x00FFFFFF FFFFFFFF |  0x00FFFFFF 00FFFFFF |
       */
      memset(status_table, 0xFF, STATUS_TABLE_SIZE(status_num));
      if (status_index > 0) {
  #if (EF_WRITE_GRAN == 1)
          byte_index = (status_index - 1) / 8;
          status_table[byte_index] &= ~(0x80 >> ((status_index - 1) % 8));
  #else
          byte_index = (status_index - 1) * (EF_WRITE_GRAN / 8);
          status_table[byte_index] = 0x00;
  #endif /* EF_WRITE_GRAN == 1 */
      }
  
      return byte_index;
  }
  
  static size_t get_status(uint8_t status_table[], size_t status_num)
  {
      size_t i = 0, status_num_bak = --status_num;
  
      while (status_num --) {
          /* get the first 0 position from end address to start address */
  #if (EF_WRITE_GRAN == 1)
          if ((status_table[status_num / 8] & (0x80 >> (status_num % 8))) == 0x00) {
              break;
          }
  #else /*  (EF_WRITE_GRAN == 8) ||  (EF_WRITE_GRAN == 32) ||  (EF_WRITE_GRAN == 64) */
          if (status_table[status_num * EF_WRITE_GRAN / 8] == 0x00) {
              break;
          }
  #endif /* EF_WRITE_GRAN == 1 */
          i++;
      }
  
      return status_num_bak - i;
  }
  
  static EfErrCode write_status(uint32_t addr, uint8_t status_table[], size_t status_num, size_t status_index)
  {
      EfErrCode result = EF_NO_ERR;
      size_t byte_index;
  
      EF_ASSERT(status_index < status_num);
      EF_ASSERT(status_table);
  
      /* set the status first */
      byte_index = set_status(status_table, status_num, status_index);
  
      /* the first status table value is all 1, so no need to write flash */
      if (byte_index == ~0UL) {
          return EF_NO_ERR;
      }
  #if (EF_WRITE_GRAN == 1)
      result = ef_port_write(addr + byte_index, (uint32_t *)&status_table[byte_index], 1);
  #else /*  (EF_WRITE_GRAN == 8) ||  (EF_WRITE_GRAN == 32) ||  (EF_WRITE_GRAN == 64) */
      /* write the status by write granularity
       * some flash (like stm32 onchip) NOT supported repeated write before erase */
      result = ef_port_write(addr + byte_index, (uint32_t *) &status_table[byte_index], EF_WRITE_GRAN / 8);
  #endif /* EF_WRITE_GRAN == 1 */
  
      return result;
  }
  
  static size_t read_status(uint32_t addr, uint8_t status_table[], size_t total_num)
  {
      EF_ASSERT(status_table);
  
      ef_port_read(addr, (uint32_t *) status_table, STATUS_TABLE_SIZE(total_num));
  
      return get_status(status_table, total_num);
  }
  
  #ifdef EF_ENV_USING_CACHE
  /*
   * It's only caching the current using status sector's empty_addr
   */
  static void update_sector_cache(uint32_t sec_addr, uint32_t empty_addr)
  {
      size_t i, empty_index = EF_SECTOR_CACHE_TABLE_SIZE;
  
      for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {
          if ((empty_addr > sec_addr) && (empty_addr < sec_addr + SECTOR_SIZE)) {
              /* update the sector empty_addr in cache */
              if (sector_cache_table[i].addr == sec_addr) {
                  sector_cache_table[i].addr = sec_addr;
                  sector_cache_table[i].empty_addr = empty_addr;
                  return;
              } else if ((sector_cache_table[i].addr == FAILED_ADDR) && (empty_index == EF_SECTOR_CACHE_TABLE_SIZE)) {
                  empty_index = i;
              }
          } else if (sector_cache_table[i].addr == sec_addr) {
              /* delete the sector which status is not current using */
              sector_cache_table[i].addr = FAILED_ADDR;
              return;
          }
      }
      /* add the sector empty_addr to cache */
      if (empty_index < EF_SECTOR_CACHE_TABLE_SIZE) {
          sector_cache_table[empty_index].addr = sec_addr;
          sector_cache_table[empty_index].empty_addr = empty_addr;
      }
  }
  
  /*
   * Get sector info from cache. It's return true when cache is hit.
   */
  static bool get_sector_from_cache(uint32_t sec_addr, uint32_t *empty_addr)
  {
      size_t i;
  
      for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {
          if (sector_cache_table[i].addr == sec_addr) {
              if (empty_addr) {
                  *empty_addr = sector_cache_table[i].empty_addr;
              }
              return true;
          }
      }
  
      return false;
  }
  
  static void update_env_cache(const char *name, size_t name_len, uint32_t addr)
  {
      size_t i, empty_index = EF_ENV_CACHE_TABLE_SIZE, min_activity_index = EF_ENV_CACHE_TABLE_SIZE;
      uint16_t name_crc = (uint16_t) (ef_calc_crc32(0, name, name_len) >> 16), min_activity = 0xFFFF;
  
      for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {
          if (addr != FAILED_ADDR) {
              /* update the ENV address in cache */
              if (env_cache_table[i].name_crc == name_crc) {
                  env_cache_table[i].addr = addr;
                  return;
              } else if ((env_cache_table[i].addr == FAILED_ADDR) && (empty_index == EF_ENV_CACHE_TABLE_SIZE)) {
                  empty_index = i;
              } else if (env_cache_table[i].addr != FAILED_ADDR) {
                  if (env_cache_table[i].active > 0) {
                      env_cache_table[i].active--;
                  }
                  if (env_cache_table[i].active < min_activity) {
                      min_activity_index = i;
                      min_activity = env_cache_table[i].active;
                  }
              }
          } else if (env_cache_table[i].name_crc == name_crc) {
              /* delete the ENV */
              env_cache_table[i].addr = FAILED_ADDR;
              env_cache_table[i].active = 0;
              return;
          }
      }
      /* add the ENV to cache, using LRU (Least Recently Used) like algorithm */
      if (empty_index < EF_ENV_CACHE_TABLE_SIZE) {
          env_cache_table[empty_index].addr = addr;
          env_cache_table[empty_index].name_crc = name_crc;
          env_cache_table[empty_index].active = 0;
      } else if (min_activity_index < EF_ENV_CACHE_TABLE_SIZE) {
          env_cache_table[min_activity_index].addr = addr;
          env_cache_table[min_activity_index].name_crc = name_crc;
          env_cache_table[min_activity_index].active = 0;
      }
  }
  
  /*
   * Get ENV info from cache. It's return true when cache is hit.
   */
  static bool get_env_from_cache(const char *name, size_t name_len, uint32_t *addr)
  {
      size_t i;
      uint16_t name_crc = (uint16_t) (ef_calc_crc32(0, name, name_len) >> 16);
  
      for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {
          if ((env_cache_table[i].addr != FAILED_ADDR) && (env_cache_table[i].name_crc == name_crc)) {
              char saved_name[EF_ENV_NAME_MAX];
              /* read the ENV name in flash */
              ef_port_read(env_cache_table[i].addr + ENV_HDR_DATA_SIZE, (uint32_t *) saved_name, EF_ENV_NAME_MAX);
              if (!strncmp(name, saved_name, name_len)) {
                  *addr = env_cache_table[i].addr;
                  if (env_cache_table[i].active >= 0xFFFF - EF_ENV_CACHE_TABLE_SIZE) {
                      env_cache_table[i].active = 0xFFFF;
                  } else {
                      env_cache_table[i].active += EF_ENV_CACHE_TABLE_SIZE;
                  }
                  return true;
              }
          }
      }
  
      return false;
  }
  #endif /* EF_ENV_USING_CACHE */
  
  /*
   * find the continue 0xFF flash address to end address
   */
  static uint32_t continue_ff_addr(uint32_t start, uint32_t end)
  {
      uint8_t buf[32], last_data = 0x00;
      size_t i, addr = start;
  
      for (; start < end; start += sizeof(buf)) {
          ef_port_read(start, (uint32_t *) buf, sizeof(buf));
          for (i = 0; i < sizeof(buf) && start + i < end; i++) {
              if (last_data != 0xFF && buf[i] == 0xFF) {
                  addr = start + i;
              }
              last_data = buf[i];
          }
      }
  
      if (last_data == 0xFF) {
          return EF_WG_ALIGN(addr);
      } else {
          return end;
      }
  }
  
  /*
   * find the next ENV address by magic word on the flash
   */
  static uint32_t find_next_env_addr(uint32_t start, uint32_t end)
  {
      uint8_t buf[32];
      uint32_t start_bak = start, i;
      uint32_t magic;
  
  #ifdef EF_ENV_USING_CACHE
      uint32_t empty_env;
  
      if (get_sector_from_cache(EF_ALIGN_DOWN(start, SECTOR_SIZE), &empty_env) && start == empty_env) {
          return FAILED_ADDR;
      }
  #endif /* EF_ENV_USING_CACHE */
  
      for (; start < end; start += (sizeof(buf) - sizeof(uint32_t))) {
          ef_port_read(start, (uint32_t *) buf, sizeof(buf));
          for (i = 0; i < sizeof(buf) - sizeof(uint32_t) && start + i < end; i++) {
              magic = buf[i] + (buf[i + 1] << 8) + (buf[i + 2] << 16) + (buf[i + 3] << 24);
              if (magic == ENV_MAGIC_WORD && (start + i - ENV_MAGIC_OFFSET) >= start_bak) {
                  return start + i - ENV_MAGIC_OFFSET;
              }
          }
      }
  
      return FAILED_ADDR;
  }
  
  static uint32_t get_next_env_addr(sector_meta_data_t sector, env_meta_data_t pre_env)
  {
      uint32_t addr = FAILED_ADDR;
  
      if (sector->status.store == SECTOR_STORE_EMPTY) {
          return FAILED_ADDR;
      }
  
      if (pre_env->addr.start == FAILED_ADDR) {
          /* the first ENV address */
          addr = sector->addr + SECTOR_HDR_DATA_SIZE;
      } else {
          if (pre_env->addr.start <= sector->addr + SECTOR_SIZE) {
              if (pre_env->crc_is_ok) {
                  addr = pre_env->addr.start + pre_env->len;
              } else {
                  /* when pre_env CRC check failed, maybe the flash has error data
                   * find_next_env_addr after pre_env address */
                  addr = pre_env->addr.start + EF_WG_ALIGN(1);
              }
              /* check and find next ENV address */
              addr = find_next_env_addr(addr, sector->addr + SECTOR_SIZE - SECTOR_HDR_DATA_SIZE);
  
              if (addr > sector->addr + SECTOR_SIZE || pre_env->len == 0) {
                  //TODO 扇区连续模式
                  return FAILED_ADDR;
              }
          } else {
              /* no ENV */
              return FAILED_ADDR;
          }
      }
  
      return addr;
  }
  
  static EfErrCode read_env(env_meta_data_t env)
  {
      struct env_hdr_data env_hdr;
      uint8_t buf[32];
      uint32_t calc_crc32 = 0, crc_data_len, env_name_addr;
      EfErrCode result = EF_NO_ERR;
      size_t len, size;
      /* read ENV header raw data */
      ef_port_read(env->addr.start, (uint32_t *)&env_hdr, sizeof(struct env_hdr_data));
      env->status = (env_status_t) get_status(env_hdr.status_table, ENV_STATUS_NUM);
      env->len = env_hdr.len;
  
      if (env->len == ~0UL || env->len > ENV_AREA_SIZE || env->len < ENV_NAME_LEN_OFFSET) {
          /* the ENV length was not write, so reserved the meta data for current ENV */
          env->len = ENV_HDR_DATA_SIZE;
          if (env->status != ENV_ERR_HDR) {
              env->status = ENV_ERR_HDR;
              EF_DEBUG("Error: The ENV @0x%08X length has an error.\n", env->addr.start);
              write_status(env->addr.start, env_hdr.status_table, ENV_STATUS_NUM, ENV_ERR_HDR);
          }
          env->crc_is_ok = false;
          return EF_READ_ERR;
      } else if (env->len > SECTOR_SIZE - SECTOR_HDR_DATA_SIZE && env->len < ENV_AREA_SIZE) {
          //TODO 扇区连续模式，或者写入长度没有写入完整
          EF_ASSERT(0);
      }
  
      /* CRC32 data len(header.name_len + header.value_len + name + value) */
      crc_data_len = env->len - ENV_NAME_LEN_OFFSET;
      /* calculate the CRC32 value */
      for (len = 0, size = 0; len < crc_data_len; len += size) {
          if (len + sizeof(buf) < crc_data_len) {
              size = sizeof(buf);
          } else {
              size = crc_data_len - len;
          }
  
          ef_port_read(env->addr.start + ENV_NAME_LEN_OFFSET + len, (uint32_t *) buf, EF_WG_ALIGN(size));
          calc_crc32 = ef_calc_crc32(calc_crc32, buf, size);
      }
      /* check CRC32 */
      if (calc_crc32 != env_hdr.crc32) {
          env->crc_is_ok = false;
          result = EF_READ_ERR;
      } else {
          env->crc_is_ok = true;
          /* the name is behind aligned ENV header */
          env_name_addr = env->addr.start + ENV_HDR_DATA_SIZE;
          ef_port_read(env_name_addr, (uint32_t *) env->name, EF_WG_ALIGN(env_hdr.name_len));
          /* the value is behind aligned name */
          env->addr.value = env_name_addr + EF_WG_ALIGN(env_hdr.name_len);
          env->value_len = env_hdr.value_len;
          env->name_len = env_hdr.name_len;
      }
  
      return result;
  }
  
  static EfErrCode read_sector_meta_data(uint32_t addr, sector_meta_data_t sector, bool traversal)
  {
      EfErrCode result = EF_NO_ERR;
      struct sector_hdr_data sec_hdr;
  
      EF_ASSERT(addr % SECTOR_SIZE == 0);
      EF_ASSERT(sector);
  
      /* read sector header raw data */
      ef_port_read(addr, (uint32_t *)&sec_hdr, sizeof(struct sector_hdr_data));
  
      sector->addr = addr;
      sector->magic = sec_hdr.magic;
      /* check magic word */
      if (sector->magic != SECTOR_MAGIC_WORD) {
          sector->check_ok = false;
          return EF_ENV_INIT_FAILED;
      }
      sector->check_ok = true;
      /* get other sector meta data */
      sector->combined = sec_hdr.combined;
      sector->status.store = (sector_store_status_t) get_status(sec_hdr.status_table.store, SECTOR_STORE_STATUS_NUM);
      sector->status.dirty = (sector_dirty_status_t) get_status(sec_hdr.status_table.dirty, SECTOR_DIRTY_STATUS_NUM);
      /* traversal all ENV and calculate the remain space size */
      if (traversal) {
          sector->remain = 0;
          sector->empty_env = sector->addr + SECTOR_HDR_DATA_SIZE;
          if (sector->status.store == SECTOR_STORE_EMPTY) {
              sector->remain = SECTOR_SIZE - SECTOR_HDR_DATA_SIZE;
          } else if (sector->status.store == SECTOR_STORE_USING) {
              struct env_meta_data env_meta;
  
  #ifdef EF_ENV_USING_CACHE
              if (get_sector_from_cache(addr, &sector->empty_env)) {
                  sector->remain = SECTOR_SIZE - (sector->empty_env - sector->addr);
                  return result;
              }
  #endif /* EF_ENV_USING_CACHE */
  
              sector->remain = SECTOR_SIZE - SECTOR_HDR_DATA_SIZE;
              env_meta.addr.start = FAILED_ADDR;
              while ((env_meta.addr.start = get_next_env_addr(sector, &env_meta)) != FAILED_ADDR) {
                  read_env(&env_meta);
                  if (!env_meta.crc_is_ok) {
                      if (env_meta.status != ENV_PRE_WRITE && env_meta.status!= ENV_ERR_HDR) {
                          EF_INFO("Error: The ENV (@0x%08X) CRC32 check failed!\n", env_meta.addr.start);
                          sector->remain = 0;
                          result = EF_READ_ERR;
                          break;
                      }
                  }
                  sector->empty_env += env_meta.len;
                  sector->remain -= env_meta.len;
              }
              /* check the empty ENV address by read continue 0xFF on flash  */
              {
                  uint32_t ff_addr;
  
                  ff_addr = continue_ff_addr(sector->empty_env, sector->addr + SECTOR_SIZE);
                  /* check the flash data is clean */
                  if (sector->empty_env != ff_addr) {
                      /* update the sector information */
                      sector->empty_env = ff_addr;
                      sector->remain = SECTOR_SIZE - (ff_addr - sector->addr);
                  }
              }
  
  #ifdef EF_ENV_USING_CACHE
              update_sector_cache(sector->addr, sector->empty_env);
  #endif
          }
      }
  
      return result;
  }
  
  static uint32_t get_next_sector_addr(sector_meta_data_t pre_sec)
  {
      uint32_t next_addr;
  
      if (pre_sec->addr == FAILED_ADDR) {
          return env_start_addr;
      } else {
          /* check ENV sector combined */
          if (pre_sec->combined == SECTOR_NOT_COMBINED) {
              next_addr = pre_sec->addr + SECTOR_SIZE;
          } else {
              next_addr = pre_sec->addr + pre_sec->combined * SECTOR_SIZE;
          }
          /* check range */
          if (next_addr < env_start_addr + ENV_AREA_SIZE) {
              return next_addr;
          } else {
              /* no sector */
              return FAILED_ADDR;
          }
      }
  }
  
  static void env_iterator(env_meta_data_t env, void *arg1, void *arg2,
          bool (*callback)(env_meta_data_t env, void *arg1, void *arg2))
  {
      struct sector_meta_data sector;
      uint32_t sec_addr;
  
      sector.addr = FAILED_ADDR;
      /* search all sectors */
      while ((sec_addr = get_next_sector_addr(&sector)) != FAILED_ADDR) {
          if (read_sector_meta_data(sec_addr, &sector, false) != EF_NO_ERR) {
              continue;
          }
          if (callback == NULL) {
              continue;
          }
          /* sector has ENV */
          if (sector.status.store == SECTOR_STORE_USING || sector.status.store == SECTOR_STORE_FULL) {
              env->addr.start = FAILED_ADDR;
              /* search all ENV */
              while ((env->addr.start = get_next_env_addr(&sector, env)) != FAILED_ADDR) {
                  read_env(env);
                  /* iterator is interrupted when callback return true */
                  if (callback(env, arg1, arg2)) {
                      return;
                  }
              }
          }
      }
  }
  
  static bool find_env_cb(env_meta_data_t env, void *arg1, void *arg2)
  {
      const char *key = (char*)arg1;
      bool *find_ok = (bool*)arg2;
      uint8_t max_len = strlen(key);
  
      if (max_len < env->name_len) {
          max_len = env->name_len;
      }
      /* check ENV */
      if (env->crc_is_ok && env->status == ENV_WRITE && !strncmp(env->name, key, max_len)) {
          *find_ok = true;
          return true;
      }
      return false;
  }
  
  static bool find_env_no_cache(const char *key, env_meta_data_t env)
  {
      bool find_ok = false;
  
      env_iterator(env, (void *)key, &find_ok, find_env_cb);
  
      return find_ok;
  }
  
  static bool find_env(const char *key, env_meta_data_t env)
  {
      bool find_ok = false;
  
  #ifdef EF_ENV_USING_CACHE
      size_t key_len = strlen(key);
  
      if (get_env_from_cache(key, key_len, &env->addr.start)) {
          read_env(env);
          return true;
      }
  #endif /* EF_ENV_USING_CACHE */
  
      find_ok = find_env_no_cache(key, env);
  
  #ifdef EF_ENV_USING_CACHE
      if (find_ok) {
          update_env_cache(key, key_len, env->addr.start);
      }
  #endif /* EF_ENV_USING_CACHE */
  
      return find_ok;
  }
  
  static bool ef_is_str(uint8_t *value, size_t len)
  {
  #define __is_print(ch)       ((unsigned int)((ch) - ' ') < 127u - ' ')
      size_t i;
  
      for (i = 0; i < len; i++) {
          if (!__is_print(value[i])) {
              return false;
          }
      }
      return true;
  }
  
  static size_t get_env(const char *key, void *value_buf, size_t buf_len, size_t *value_len)
  {
      struct env_meta_data env;
      size_t read_len = 0;
  
      if (find_env(key, &env)) {
          if (value_len) {
              *value_len = env.value_len;
          }
          if (buf_len > env.value_len) {
              read_len = env.value_len;
          } else {
              read_len = buf_len;
          }
          ef_port_read(env.addr.value, (uint32_t *) value_buf, read_len);
      }
  
      return read_len;
  }
  
  /**
   * Get a blob ENV value by key name.
   *
   * @param key ENV name
   * @param value_buf ENV blob buffer
   * @param buf_len ENV blob buffer length
   * @param saved_value_len return the length of the value saved on the flash, 0: NOT found
   *
   * @return the actually get size on successful
   */
  size_t ef_get_env_blob(const char *key, void *value_buf, size_t buf_len, size_t *saved_value_len)
  {
      size_t read_len = 0;
  
      if (!init_ok) {
          EF_INFO("ENV isn't initialize OK.\n");
          return 0;
      }
  
      /* lock the ENV cache */
      ef_port_env_lock();
  
      read_len = get_env(key, value_buf, buf_len, saved_value_len);
  
      /* unlock the ENV cache */
      ef_port_env_unlock();
  
      return read_len;
  }
  
  /**
   * Get an ENV value by key name.
   *
   * @note this function is NOT supported reentrant
   * @note this function is DEPRECATED
   *
   * @param key ENV name
   *
   * @return value
   */
  char *ef_get_env(const char *key)
  {
      static char value[EF_STR_ENV_VALUE_MAX_SIZE + 1];
      size_t get_size;
  
      if ((get_size = ef_get_env_blob(key, value, EF_STR_ENV_VALUE_MAX_SIZE, NULL)) > 0) {
          /* the return value must be string */
          if (ef_is_str((uint8_t *)value, get_size)) {
              value[get_size] = '\0';
              return value;
          } else {
              EF_INFO("Warning: The ENV value isn't string. Could not be returned\n");
              return NULL;
          }
      }
  
      return NULL;
  }
  
  static EfErrCode write_env_hdr(uint32_t addr, env_hdr_data_t env_hdr) {
      EfErrCode result = EF_NO_ERR;
      /* write the status will by write granularity */
      result = write_status(addr, env_hdr->status_table, ENV_STATUS_NUM, ENV_PRE_WRITE);
      if (result != EF_NO_ERR) {
          return result;
      }
      /* write other header data */
      result = ef_port_write(addr + ENV_MAGIC_OFFSET, &env_hdr->magic, sizeof(struct env_hdr_data) - ENV_MAGIC_OFFSET);
  
      return result;
  }
  
  static EfErrCode format_sector(uint32_t addr, uint32_t combined_value)
  {
      EfErrCode result = EF_NO_ERR;
      struct sector_hdr_data sec_hdr;
  
      EF_ASSERT(addr % SECTOR_SIZE == 0);
  
      result = ef_port_erase(addr, SECTOR_SIZE);
      if (result == EF_NO_ERR) {
          /* initialize the header data */
          memset(&sec_hdr, 0xFF, sizeof(struct sector_hdr_data));
          set_status(sec_hdr.status_table.store, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_EMPTY);
          set_status(sec_hdr.status_table.dirty, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_FALSE);
          sec_hdr.magic = SECTOR_MAGIC_WORD;
          sec_hdr.combined = combined_value;
          sec_hdr.reserved = 0xFFFFFFFF;
          /* save the header */
          result = ef_port_write(addr, (uint32_t *)&sec_hdr, sizeof(struct sector_hdr_data));
  
  #ifdef EF_ENV_USING_CACHE
          /* delete the sector cache */
          update_sector_cache(addr, addr + SECTOR_SIZE);
  #endif /* EF_ENV_USING_CACHE */
      }
  
      return result;
  }
  
  static EfErrCode update_sec_status(sector_meta_data_t sector, size_t new_env_len, bool *is_full)
  {
      uint8_t status_table[STORE_STATUS_TABLE_SIZE];
      EfErrCode result = EF_NO_ERR;
      /* change the current sector status */
      if (sector->status.store == SECTOR_STORE_EMPTY) {
          /* change the sector status to using */
          result = write_status(sector->addr, status_table, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_USING);
      } else if (sector->status.store == SECTOR_STORE_USING) {
          /* check remain size */
          if (sector->remain < EF_SEC_REMAIN_THRESHOLD || sector->remain - new_env_len < EF_SEC_REMAIN_THRESHOLD) {
              /* change the sector status to full */
              result = write_status(sector->addr, status_table, SECTOR_STORE_STATUS_NUM, SECTOR_STORE_FULL);
  
  #ifdef EF_ENV_USING_CACHE
              /* delete the sector cache */
              update_sector_cache(sector->addr, sector->addr + SECTOR_SIZE);
  #endif /* EF_ENV_USING_CACHE */
  
              if (is_full) {
                  *is_full = true;
              }
          } else if (is_full) {
              *is_full = false;
          }
      }
  
      return result;
  }
  
  static void sector_iterator(sector_meta_data_t sector, sector_store_status_t status, void *arg1, void *arg2,
          bool (*callback)(sector_meta_data_t sector, void *arg1, void *arg2), bool traversal_env) {
      uint32_t sec_addr;
  
      /* search all sectors */
      sector->addr = FAILED_ADDR;
      while ((sec_addr = get_next_sector_addr(sector)) != FAILED_ADDR) {
          read_sector_meta_data(sec_addr, sector, false);
          if (status == SECTOR_STORE_UNUSED || status == sector->status.store) {
              if (traversal_env) {
                  read_sector_meta_data(sec_addr, sector, traversal_env);
              }
              /* iterator is interrupted when callback return true */
              if (callback && callback(sector, arg1, arg2)) {
                  return;
              }
          }
      }
  }
  
  static bool sector_statistics_cb(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      size_t *empty_sector = (size_t*)arg1, *using_sector = (size_t*)arg2;
  
      if (sector->check_ok && sector->status.store == SECTOR_STORE_EMPTY) {
          (*empty_sector)++;
      } else if (sector->check_ok && sector->status.store == SECTOR_STORE_USING) {
          (*using_sector)++;
      }
  
      return false;
  }
  
  static bool alloc_env_cb(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      size_t *env_size = (size_t*)arg1;
      uint32_t *empty_env = (uint32_t*)arg2;
  
      /* 1. sector has space
       * 2. the NO dirty sector
       * 3. the dirty sector only when the gc_request is false */
      if (sector->check_ok && sector->remain > *env_size
              && ((sector->status.dirty == SECTOR_DIRTY_FALSE)
                      || (sector->status.dirty == SECTOR_DIRTY_TRUE && !gc_request))) {
          *empty_env = sector->empty_env;
          return true;
      }
  
      return false;
  }
  
  static uint32_t alloc_env(sector_meta_data_t sector, size_t env_size)
  {
      uint32_t empty_env = FAILED_ADDR;
      size_t empty_sector = 0, using_sector = 0;
  
      /* sector status statistics */
      sector_iterator(sector, SECTOR_STORE_UNUSED, &empty_sector, &using_sector, sector_statistics_cb, false);
      if (using_sector > 0) {
          /* alloc the ENV from the using status sector first */
          sector_iterator(sector, SECTOR_STORE_USING, &env_size, &empty_env, alloc_env_cb, true);
      }
      if (empty_sector > 0 && empty_env == FAILED_ADDR) {
          if (empty_sector > EF_GC_EMPTY_SEC_THRESHOLD || gc_request) {
              sector_iterator(sector, SECTOR_STORE_EMPTY, &env_size, &empty_env, alloc_env_cb, true);
          } else {
              /* no space for new ENV now will GC and retry */
              EF_DEBUG("Trigger a GC check after alloc ENV failed.\n");
              gc_request = true;
          }
      }
  
      return empty_env;
  }
  
  static EfErrCode del_env(const char *key, env_meta_data_t old_env, bool complete_del) {
      EfErrCode result = EF_NO_ERR;
      uint32_t dirty_status_addr;
      static bool last_is_complete_del = false;
  
  #if (ENV_STATUS_TABLE_SIZE >= DIRTY_STATUS_TABLE_SIZE)
      uint8_t status_table[ENV_STATUS_TABLE_SIZE];
  #else
      uint8_t status_table[DIRTY_STATUS_TABLE_SIZE];
  #endif
  
      /* need find ENV */
      if (!old_env) {
          struct env_meta_data env;
          /* find ENV */
          if (find_env(key, &env)) {
              old_env = &env;
          } else {
              EF_DEBUG("Not found '%s' in ENV.\n", key);
              return EF_ENV_NAME_ERR;
          }
      }
      /* change and save the new status */
      if (!complete_del) {
          result = write_status(old_env->addr.start, status_table, ENV_STATUS_NUM, ENV_PRE_DELETE);
          last_is_complete_del = true;
      } else {
          result = write_status(old_env->addr.start, status_table, ENV_STATUS_NUM, ENV_DELETED);
  
          if (!last_is_complete_del && result == EF_NO_ERR) {
  #ifdef EF_ENV_USING_CACHE
              /* only delete the ENV in flash and cache when only using del_env(key, env, true) in ef_del_env() */
              update_env_cache(key, strlen(key), FAILED_ADDR);
  #endif /* EF_ENV_USING_CACHE */
          }
  
          last_is_complete_del = false;
      }
  
      dirty_status_addr = EF_ALIGN_DOWN(old_env->addr.start, SECTOR_SIZE) + SECTOR_DIRTY_OFFSET;
      /* read and change the sector dirty status */
      if (result == EF_NO_ERR
              && read_status(dirty_status_addr, status_table, SECTOR_DIRTY_STATUS_NUM) == SECTOR_DIRTY_FALSE) {
          result = write_status(dirty_status_addr, status_table, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_TRUE);
      }
  
      return result;
  }
  
  /*
   * move the ENV to new space
   */
  static EfErrCode move_env(env_meta_data_t env)
  {
      EfErrCode result = EF_NO_ERR;
      uint8_t status_table[ENV_STATUS_TABLE_SIZE];
      uint32_t env_addr;
      struct sector_meta_data sector;
  
      /* prepare to delete the current ENV */
      if (env->status == ENV_WRITE) {
          del_env(NULL, env, false);
      }
  
      if ((env_addr = alloc_env(&sector, env->len)) != FAILED_ADDR) {
          if (in_recovery_check) {
              struct env_meta_data env_bak;
              char name[EF_ENV_NAME_MAX + 1] = { 0 };
              strncpy(name, env->name, env->name_len);
              /* check the ENV in flash is already create success */
              if (find_env_no_cache(name, &env_bak)) {
                  /* already create success, don't need to duplicate */
                  result = EF_NO_ERR;
                  goto __exit;
              }
          }
      } else {
          return EF_ENV_FULL;
      }
      /* start move the ENV */
      {
          uint8_t buf[32];
          size_t len, size, env_len = env->len;
  
          /* update the new ENV sector status first */
          update_sec_status(&sector, env->len, NULL);
  
          write_status(env_addr, status_table, ENV_STATUS_NUM, ENV_PRE_WRITE);
          env_len -= ENV_MAGIC_OFFSET;
          for (len = 0, size = 0; len < env_len; len += size) {
              if (len + sizeof(buf) < env_len) {
                  size = sizeof(buf);
              } else {
                  size = env_len - len;
              }
              ef_port_read(env->addr.start + ENV_MAGIC_OFFSET + len, (uint32_t *) buf, EF_WG_ALIGN(size));
              result = ef_port_write(env_addr + ENV_MAGIC_OFFSET + len, (uint32_t *) buf, size);
          }
          write_status(env_addr, status_table, ENV_STATUS_NUM, ENV_WRITE);
  
  #ifdef EF_ENV_USING_CACHE
          update_sector_cache(EF_ALIGN_DOWN(env_addr, SECTOR_SIZE),
                  env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env->name_len) + EF_WG_ALIGN(env->value_len));
          update_env_cache(env->name, env->name_len, env_addr);
  #endif /* EF_ENV_USING_CACHE */
      }
  
      EF_DEBUG("Moved the ENV (%.*s) from 0x%08X to 0x%08X.\n", env->name_len, env->name, env->addr.start, env_addr);
  
  __exit:
      del_env(NULL, env, true);
  
      return result;
  }
  
  static uint32_t new_env(sector_meta_data_t sector, size_t env_size)
  {
      bool already_gc = false;
      uint32_t empty_env = FAILED_ADDR;
  
  __retry:
  
      if ((empty_env = alloc_env(sector, env_size)) == FAILED_ADDR && gc_request && !already_gc) {
          EF_DEBUG("Warning: Alloc an ENV (size %d) failed when new ENV. Now will GC then retry.\n", env_size);
          gc_collect();
          already_gc = true;
          goto __retry;
      }
  
      return empty_env;
  }
  
  static uint32_t new_env_by_kv(sector_meta_data_t sector, size_t key_len, size_t buf_len)
  {
      size_t env_len = ENV_HDR_DATA_SIZE + EF_WG_ALIGN(key_len) + EF_WG_ALIGN(buf_len);
  
      return new_env(sector, env_len);
  }
  
  static bool gc_check_cb(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      size_t *empty_sec = (size_t*)arg1;
  
      if (sector->check_ok) {
          *empty_sec = *empty_sec + 1;
      }
  
      return false;
  
  }
  
  static bool do_gc(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      struct env_meta_data env;
  
      if (sector->check_ok && (sector->status.dirty == SECTOR_DIRTY_TRUE || sector->status.dirty == SECTOR_DIRTY_GC)) {
          uint8_t status_table[DIRTY_STATUS_TABLE_SIZE];
          /* change the sector status to GC */
          write_status(sector->addr + SECTOR_DIRTY_OFFSET, status_table, SECTOR_DIRTY_STATUS_NUM, SECTOR_DIRTY_GC);
          /* search all ENV */
          env.addr.start = FAILED_ADDR;
          while ((env.addr.start = get_next_env_addr(sector, &env)) != FAILED_ADDR) {
              read_env(&env);
              if (env.crc_is_ok && (env.status == ENV_WRITE || env.status == ENV_PRE_DELETE)) {
                  /* move the ENV to new space */
                  if (move_env(&env) != EF_NO_ERR) {
                      EF_DEBUG("Error: Moved the ENV (%.*s) for GC failed.\n", env.name_len, env.name);
                  }
              }
          }
          format_sector(sector->addr, SECTOR_NOT_COMBINED);
          EF_DEBUG("Collect a sector @0x%08X\n", sector->addr);
      }
  
      return false;
  }
  
  /*
   * The GC will be triggered on the following scene:
   * 1. alloc an ENV when the flash not has enough space
   * 2. write an ENV then the flash not has enough space
   */
  static void gc_collect(void)
  {
      struct sector_meta_data sector;
      size_t empty_sec = 0;
  
      /* GC check the empty sector number */
      sector_iterator(&sector, SECTOR_STORE_EMPTY, &empty_sec, NULL, gc_check_cb, false);
  
      /* do GC collect */
      EF_DEBUG("The remain empty sector is %d, GC threshold is %d.\n", empty_sec, EF_GC_EMPTY_SEC_THRESHOLD);
      if (empty_sec <= EF_GC_EMPTY_SEC_THRESHOLD) {
          sector_iterator(&sector, SECTOR_STORE_UNUSED, NULL, NULL, do_gc, false);
      }
  
      gc_request = false;
  }
  
  static EfErrCode align_write(uint32_t addr, const uint32_t *buf, size_t size)
  {
      EfErrCode result = EF_NO_ERR;
      size_t align_remain;
  
  #if (EF_WRITE_GRAN / 8 > 0)
      uint8_t align_data[EF_WRITE_GRAN / 8];
      size_t align_data_size = sizeof(align_data);
  #else
      /* For compatibility with C89 */
      uint8_t align_data_u8, *align_data = &align_data_u8;
      size_t align_data_size = 1;
  #endif
  
      memset(align_data, 0xFF, align_data_size);
      result = ef_port_write(addr, buf, EF_WG_ALIGN_DOWN(size));
  
      align_remain = size - EF_WG_ALIGN_DOWN(size);
      if (result == EF_NO_ERR && align_remain) {
          memcpy(align_data, (uint8_t *)buf + EF_WG_ALIGN_DOWN(size), align_remain);
          result = ef_port_write(addr + EF_WG_ALIGN_DOWN(size), (uint32_t *) align_data, align_data_size);
      }
  
      return result;
  }
  
  static EfErrCode create_env_blob(sector_meta_data_t sector, const char *key, const void *value, size_t len)
  {
      EfErrCode result = EF_NO_ERR;
      struct env_hdr_data env_hdr;
      bool is_full = false;
      uint32_t env_addr = sector->empty_env;
  
      if (strlen(key) > EF_ENV_NAME_MAX) {
          EF_INFO("Error: The ENV name length is more than %d\n", EF_ENV_NAME_MAX);
          return EF_ENV_NAME_ERR;
      }
  
      memset(&env_hdr, 0xFF, sizeof(struct env_hdr_data));
      env_hdr.magic = ENV_MAGIC_WORD;
      env_hdr.name_len = strlen(key);
      env_hdr.value_len = len;
      env_hdr.len = ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len) + EF_WG_ALIGN(env_hdr.value_len);
  
      if (env_hdr.len > SECTOR_SIZE - SECTOR_HDR_DATA_SIZE) {
          EF_INFO("Error: The ENV size is too big\n");
          return EF_ENV_FULL;
      }
  
      if (env_addr != FAILED_ADDR || (env_addr = new_env(sector, env_hdr.len)) != FAILED_ADDR) {
          size_t align_remain;
          /* update the sector status */
          if (result == EF_NO_ERR) {
              result = update_sec_status(sector, env_hdr.len, &is_full);
          }
          if (result == EF_NO_ERR) {
              uint8_t ff = 0xFF;
              /* start calculate CRC32 */
              env_hdr.crc32 = ef_calc_crc32(0, &env_hdr.name_len, ENV_HDR_DATA_SIZE - ENV_NAME_LEN_OFFSET);
              env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, key, env_hdr.name_len);
              align_remain = EF_WG_ALIGN(env_hdr.name_len) - env_hdr.name_len;
              while (align_remain--) {
                  env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, &ff, 1);
              }
              env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, value, env_hdr.value_len);
              align_remain = EF_WG_ALIGN(env_hdr.value_len) - env_hdr.value_len;
              while (align_remain--) {
                  env_hdr.crc32 = ef_calc_crc32(env_hdr.crc32, &ff, 1);
              }
              /* write ENV header data */
              result = write_env_hdr(env_addr, &env_hdr);
  
          }
          /* write key name */
          if (result == EF_NO_ERR) {
              result = align_write(env_addr + ENV_HDR_DATA_SIZE, (uint32_t *) key, env_hdr.name_len);
  
  #ifdef EF_ENV_USING_CACHE
              if (!is_full) {
                  update_sector_cache(sector->addr,
                          env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len) + EF_WG_ALIGN(env_hdr.value_len));
              }
              update_env_cache(key, env_hdr.name_len, env_addr);
  #endif /* EF_ENV_USING_CACHE */
          }
          /* write value */
          if (result == EF_NO_ERR) {
              result = align_write(env_addr + ENV_HDR_DATA_SIZE + EF_WG_ALIGN(env_hdr.name_len), (uint32_t*)value,
                      env_hdr.value_len);
          }
          /* change the ENV status to ENV_WRITE */
          if (result == EF_NO_ERR) {
              result = write_status(env_addr, env_hdr.status_table, ENV_STATUS_NUM, ENV_WRITE);
          }
          /* trigger GC collect when current sector is full */
          if (result == EF_NO_ERR && is_full) {
              EF_DEBUG("Trigger a GC check after created ENV.\n");
              gc_request = true;
          }
      } else {
          result = EF_ENV_FULL;
      }
  
      return result;
  }
  
  /**
   * Delete an ENV.
   *
   * @param key ENV name
   *
   * @return result
   */
  EfErrCode ef_del_env(const char *key)
  {
      EfErrCode result = EF_NO_ERR;
  
      if (!init_ok) {
          EF_INFO("Error: ENV isn't initialize OK.\n");
          return EF_ENV_INIT_FAILED;
      }
  
      /* lock the ENV cache */
      ef_port_env_lock();
  
      result = del_env(key, NULL, true);
  
      /* unlock the ENV cache */
      ef_port_env_unlock();
  
      return result;
  }
  
  /**
   * The same to ef_del_env on this mode
   * It's compatibility with older versions (less then V4.0).
   *
   * @note this function is DEPRECATED
   *
   * @param key ENV name
   *
   * @return result
   */
  EfErrCode ef_del_and_save_env(const char *key)
  {
      return ef_del_env(key);
  }
  
  static EfErrCode set_env(const char *key, const void *value_buf, size_t buf_len)
  {
      EfErrCode result = EF_NO_ERR;
      static struct env_meta_data env;
      static struct sector_meta_data sector;
      bool env_is_found = false;
  
      if (value_buf == NULL) {
          result = del_env(key, NULL, true);
      } else {
          /* make sure the flash has enough space */
          if (new_env_by_kv(&sector, strlen(key), buf_len) == FAILED_ADDR) {
              return EF_ENV_FULL;
          }
          env_is_found = find_env(key, &env);
          /* prepare to delete the old ENV */
          if (env_is_found) {
              result = del_env(key, &env, false);
          }
          /* create the new ENV */
          if (result == EF_NO_ERR) {
              result = create_env_blob(&sector, key, value_buf, buf_len);
          }
          /* delete the old ENV */
          if (env_is_found && result == EF_NO_ERR) {
              result = del_env(key, &env, true);
          }
          /* process the GC after set ENV */
          if (gc_request) {
              gc_collect();
          }
      }
  
      return result;
  }
  
  /**
   * Set a blob ENV. If it value is NULL, delete it.
   * If not find it in flash, then create it.
   *
   * @param key ENV name
   * @param value ENV value
   * @param len ENV value length
   *
   * @return result
   */
  EfErrCode ef_set_env_blob(const char *key, const void *value_buf, size_t buf_len)
  {
      EfErrCode result = EF_NO_ERR;
  
  
      if (!init_ok) {
          EF_INFO("ENV isn't initialize OK.\n");
          return EF_ENV_INIT_FAILED;
      }
  
      /* lock the ENV cache */
      ef_port_env_lock();
  
      result = set_env(key, value_buf, buf_len);
  
      /* unlock the ENV cache */
      ef_port_env_unlock();
  
      return result;
  }
  
  /**
   * Set a string ENV. If it value is NULL, delete it.
   * If not find it in flash, then create it.
   *
   * @param key ENV name
   * @param value ENV value
   *
   * @return result
   */
  EfErrCode ef_set_env(const char *key, const char *value)
  {
      return ef_set_env_blob(key, value, strlen(value));
  }
  
  /**
   * The same to ef_set_env on this mode.
   * It's compatibility with older versions (less then V4.0).
   *
   * @note this function is DEPRECATED
   *
   * @param key ENV name
   * @param value ENV value
   *
   * @return result
   */
  EfErrCode ef_set_and_save_env(const char *key, const char *value)
  {
      return ef_set_env_blob(key, value, strlen(value));
  }
  
  /**
   * Save ENV to flash.
   *
   * @note this function is DEPRECATED
   */
  EfErrCode ef_save_env(void)
  {
      /* do nothing not cur mode */
      return EF_NO_ERR;
  }
  
  /**
   * ENV set default.
   *
   * @return result
   */
  EfErrCode ef_env_set_default(void)
  {
      EfErrCode result = EF_NO_ERR;
      uint32_t addr, i, value_len;
      struct sector_meta_data sector;
  
      EF_ASSERT(default_env_set);
      EF_ASSERT(default_env_set_size);
  
      /* lock the ENV cache */
      ef_port_env_lock();
      /* format all sectors */
      for (addr = env_start_addr; addr < env_start_addr + ENV_AREA_SIZE; addr += SECTOR_SIZE) {
          result = format_sector(addr, SECTOR_NOT_COMBINED);
          if (result != EF_NO_ERR) {
              goto __exit;
          }
      }
      /* create default ENV */
      for (i = 0; i < default_env_set_size; i++) {
          /* It seems to be a string when value length is 0.
           * This mechanism is for compatibility with older versions (less then V4.0). */
          if (default_env_set[i].value_len == 0) {
              value_len = strlen((char*)default_env_set[i].value);
          } else {
              value_len = default_env_set[i].value_len;
          }
          sector.empty_env = FAILED_ADDR;
          create_env_blob(&sector, default_env_set[i].key, default_env_set[i].value, value_len);
          if (result != EF_NO_ERR) {
              goto __exit;
          }
      }
  
  __exit:
      /* unlock the ENV cache */
      ef_port_env_unlock();
  
      return result;
  }
  
  static bool print_env_cb(env_meta_data_t env, void *arg1, void *arg2)
  {
      bool value_is_str = true, print_value = false;
      size_t *using_size = (size_t*)arg1;
  
      if (env->crc_is_ok) {
          /* calculate the total using flash size */
          *using_size += env->len;
          /* check ENV */
          if (env->status == ENV_WRITE) {
              ef_print("%.*s=", env->name_len, env->name);
  
              if (env->value_len < EF_STR_ENV_VALUE_MAX_SIZE ) {
                  uint8_t buf[32];
                  size_t len, size;
  __reload:
                  /* check the value is string */
                  for (len = 0, size = 0; len < env->value_len; len += size) {
                      if (len + sizeof(buf) < env->value_len) {
                          size = sizeof(buf);
                      } else {
                          size = env->value_len - len;
                      }
                      ef_port_read(env->addr.value + len, (uint32_t *) buf, EF_WG_ALIGN(size));
                      if (print_value) {
                          ef_print("%.*s", size, buf);
                      } else if (!ef_is_str(buf, size)) {
                          value_is_str = false;
                          break;
                      }
                  }
              } else {
                  value_is_str = false;
              }
              if (value_is_str && !print_value) {
                  print_value = true;
                  goto __reload;
              } else if (!value_is_str) {
                  ef_print("blob @0x%08X %dbytes", env->addr.value, env->value_len);
              }
              ef_print("\n");
          }
      }
  
      return false;
  }
  
  
  /**
   * Print ENV.
   */
  void ef_print_env(void)
  {
      struct env_meta_data env;
      size_t using_size = 0;
  
      if (!init_ok) {
          EF_INFO("ENV isn't initialize OK.\n");
          return;
      }
  
      /* lock the ENV cache */
      ef_port_env_lock();
  
      env_iterator(&env, &using_size, NULL, print_env_cb);
  
      ef_print("\nmode: next generation\n");
      ef_print("size: %lu/%lu bytes.\n", using_size + (SECTOR_NUM - EF_GC_EMPTY_SEC_THRESHOLD) * SECTOR_HDR_DATA_SIZE,
              ENV_AREA_SIZE - SECTOR_SIZE * EF_GC_EMPTY_SEC_THRESHOLD);
  
      /* unlock the ENV cache */
      ef_port_env_unlock();
  }
  
  #ifdef EF_ENV_AUTO_UPDATE
  /*
   * Auto update ENV to latest default when current EF_ENV_VER_NUM is changed.
   */
  static void env_auto_update(void)
  {
      size_t saved_ver_num, setting_ver_num = EF_ENV_VER_NUM;
  
      if (get_env(VER_NUM_ENV_NAME, &saved_ver_num, sizeof(size_t), NULL) > 0) {
          /* check version number */
          if (saved_ver_num != setting_ver_num) {
              struct env_meta_data env;
              size_t i, value_len;
              struct sector_meta_data sector;
              EF_DEBUG("Update the ENV from version %d to %d.\n", saved_ver_num, setting_ver_num);
              for (i = 0; i < default_env_set_size; i++) {
                  /* add a new ENV when it's not found */
                  if (!find_env(default_env_set[i].key, &env)) {
                      /* It seems to be a string when value length is 0.
                       * This mechanism is for compatibility with older versions (less then V4.0). */
                      if (default_env_set[i].value_len == 0) {
                          value_len = strlen(default_env_set[i].value);
                      } else {
                          value_len = default_env_set[i].value_len;
                      }
                      sector.empty_env = FAILED_ADDR;
                      create_env_blob(&sector, default_env_set[i].key, default_env_set[i].value, value_len);
                  }
              }
          } else {
              /* version number not changed now return */
              return;
          }
      }
  
      set_env(VER_NUM_ENV_NAME, &setting_ver_num, sizeof(size_t));
  }
  #endif /* EF_ENV_AUTO_UPDATE */
  
  static bool check_sec_hdr_cb(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      if (!sector->check_ok) {
          size_t *failed_count = (size_t*)arg1;
  
          EF_INFO("Warning: Sector header check failed. Format this sector (0x%08x).\n", sector->addr);
          (*failed_count) ++;
          format_sector(sector->addr, SECTOR_NOT_COMBINED);
      }
  
      return false;
  }
  
  static bool check_and_recovery_gc_cb(sector_meta_data_t sector, void *arg1, void *arg2)
  {
      if (sector->check_ok && sector->status.dirty == SECTOR_DIRTY_GC) {
          /* make sure the GC request flag to true */
          gc_request = true;
          /* resume the GC operate */
          gc_collect();
      }
  
      return false;
  }
  
  static bool check_and_recovery_env_cb(env_meta_data_t env, void *arg1, void *arg2)
  {
      /* recovery the prepare deleted ENV */
      if (env->crc_is_ok && env->status == ENV_PRE_DELETE) {
          EF_INFO("Found an ENV (%.*s) which has changed value failed. Now will recovery it.\n", env->name_len, env->name);
          /* recovery the old ENV */
          if (move_env(env) == EF_NO_ERR) {
              EF_DEBUG("Recovery the ENV successful.\n");
          } else {
              EF_DEBUG("Warning: Moved an ENV (size %d) failed when recovery. Now will GC then retry.\n", env->len);
              return true;
          }
      } else if (env->status == ENV_PRE_WRITE) {
          uint8_t status_table[ENV_STATUS_TABLE_SIZE];
          /* the ENV has not write finish, change the status to error */
          //TODO 绘制异常处理的状态装换图
          write_status(env->addr.start, status_table, ENV_STATUS_NUM, ENV_ERR_HDR);
          return true;
      }
  
      return false;
  }
  
  /**
   * Check and load the flash ENV meta data.
   *
   * @return result
   */
  EfErrCode ef_load_env(void)
  {
      EfErrCode result = EF_NO_ERR;
      struct env_meta_data env;
      struct sector_meta_data sector;
      size_t check_failed_count = 0;
  
      /* lock the ENV cache */
      ef_port_env_lock();
  
      in_recovery_check = true;
      /* check all sector header */
      sector_iterator(&sector, SECTOR_STORE_UNUSED, &check_failed_count, NULL, check_sec_hdr_cb, false);
      /* all sector header check failed */
      if (check_failed_count == SECTOR_NUM) {
          EF_INFO("Warning: All sector header check failed. Set it to default.\n");
          ef_env_set_default();
      }
      /* check all sector header for recovery GC */
      sector_iterator(&sector, SECTOR_STORE_UNUSED, NULL, NULL, check_and_recovery_gc_cb, false);
  
  __retry:
      /* check all ENV for recovery */
      env_iterator(&env, NULL, NULL, check_and_recovery_env_cb);
      if (gc_request) {
          gc_collect();
          goto __retry;
      }
  
      in_recovery_check = false;
  
      /* unlock the ENV cache */
      ef_port_env_unlock();
  
      return result;
  }
  
  /**
   * Flash ENV initialize.
   *
   * @param default_env default ENV set for user
   * @param default_env_size default ENV set size
   *
   * @return result
   */
  EfErrCode ef_env_init(ef_env const *default_env, size_t default_env_size) {
      EfErrCode result = EF_NO_ERR;
  
  #ifdef EF_ENV_USING_CACHE
      size_t i;
  #endif
  
      EF_ASSERT(default_env);
      EF_ASSERT(ENV_AREA_SIZE);
      /* must be aligned with erase_min_size */
      EF_ASSERT(ENV_AREA_SIZE % EF_ERASE_MIN_SIZE == 0);
      /* sector number must be greater than or equal to 2 */
      EF_ASSERT(SECTOR_NUM >= 2);
      /* must be aligned with write granularity */
      EF_ASSERT((EF_STR_ENV_VALUE_MAX_SIZE * 8) % EF_WRITE_GRAN == 0);
  
      if (init_ok) {
          return EF_NO_ERR;
      }
  
  #ifdef EF_ENV_USING_CACHE
      for (i = 0; i < EF_SECTOR_CACHE_TABLE_SIZE; i++) {
          sector_cache_table[i].addr = FAILED_ADDR;
      }
      for (i = 0; i < EF_ENV_CACHE_TABLE_SIZE; i++) {
          env_cache_table[i].addr = FAILED_ADDR;
      }
  #endif /* EF_ENV_USING_CACHE */
  
      env_start_addr = EF_START_ADDR;
      default_env_set = default_env;
      default_env_set_size = default_env_size;
  
      EF_DEBUG("ENV start address is 0x%08X, size is %d bytes.\n", EF_START_ADDR, ENV_AREA_SIZE);
  
      result = ef_load_env();
  
  #ifdef EF_ENV_AUTO_UPDATE
      if (result == EF_NO_ERR) {
          env_auto_update();
      }
  #endif
  
      if (result == EF_NO_ERR) {
          init_ok = true;
      }
  
      return result;
  }
  
  #endif /* defined(EF_USING_ENV) && !defined(EF_ENV_USING_LEGACY_MODE) */
  