/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#ifdef NEW_NV_RECORD_ENABLED
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "cmsis.h"
#include "nvrecord_extension.h"
#include "hal_trace.h"
#include "crc32.h"
#include "hal_sleep.h"
#include "hal_norflash.h"
#include "norflash_api.h"
#include "norflash_drv.h"
#include "nvrecord_bt.h"
#include "nvrecord_env.h"
#include "nvrecord_ble.h"
#include "nvrecord_dma_config.h"
#include "nvrecord_fp_account_key.h"
#include "customparam_section.h"
#include "mpu.h"
#include "besbt.h"
#include "hal_timer.h"

extern uint32_t __userdata_start[];
extern uint32_t __userdata_end[];
extern void nvrecord_rebuild_system_env(struct nvrecord_env_t* pSystemEnv);
extern void nvrecord_rebuild_paired_bt_dev_info(NV_RECORD_PAIRED_BT_DEV_INFO_T* pPairedBtInfo);
#ifdef GFPS_ENABLED
extern void nvrecord_rebuild_fp_account_key(NV_FP_ACCOUNT_KEY_RECORD_T* pFpAccountKey);
#endif
#ifdef NVREC_BAIDU_DATA_SECTION
extern void nvrecord_rebuild_dma_configuration(NV_DMA_CONFIGURATION_T* pDmaConfig);
#endif

typedef enum
{
    NV_STATE_IDLE,
    NV_STATE_MAIN_ERASING,
    NV_STATE_MAIN_ERASED,
    NV_STATE_MAIN_WRITTING,
    NV_STATE_MAIN_WRITTEN,
    NV_STATE_MAIN_DONE,
    NV_STATE_BAK_ERASING,
    NV_STATE_BAK_ERASED,
    NV_STATE_BAK_WRITTING,
    NV_STATE_BAK_WRITTEN,
    NV_STATE_BAK_DONE,
}NV_STATE;

typedef struct
{
    bool is_update;
    NV_STATE state;
    uint32_t written_size;
}NV_FLUSH_STATE;

static NV_FLUSH_STATE nv_flsh_state;
static bool nvrec_init = false;
static uint32_t _user_data_main_start;
static uint32_t _user_data_bak_start;
static uint8_t _nv_burn_buf[NV_EXTENSION_PAGE_SIZE];

NV_EXTENSION_RECORD_T *nvrecord_extension_p  = NULL;

/*
 *Note: the NV_EXTENSION_MIRROR_RAM_SIZE must be power of 2
 */
#if defined(__ARM_ARCH_8M_MAIN__)
#define __NV_BUF_MPU_ALIGNED __ALIGNED(0x20)
#else
/*
 * armv7 mpu require the address must be aligned to the section size and
 * the section size must be algined to power of 2
 */
#define __NV_BUF_MPU_ALIGNED __ALIGNED(NV_EXTENSION_MIRROR_RAM_SIZE)
#endif

static NV_MIRROR_BUF_T local_extension_data __NV_BUF_MPU_ALIGNED
                            __attribute__((section(".sram_data"))) =
{
    .nv_record = {
        {  // header
            NV_EXTENSION_MAGIC_NUMBER,
            NV_EXTENSION_MAJOR_VERSION,
            NV_EXTENSION_MINOR_VERSION,
            NV_EXTENSION_VALID_LEN,
            0,
        },

        {  // system info
        },

        {  // bt_pair_info
            0
        },

        {  // ble_pair_info

        },

#ifdef TWS_SYSTEM_ENABLED
        {  // tws_info

        },
#endif

#ifdef GFPS_ENABLED
        {  // fp_account_key_rec
            0
        },
#endif

#ifdef NVREC_BAIDU_DATA_SECTION
        {  // dma_config
            BAIDU_DATA_DEF_FM_FREQ,
        },
#endif

#ifdef TILE_DATAPATH
        {
            {0}
        },
#endif

#ifdef OTA_ENABLED
        {
            {0,},
        },
#endif

#if defined(BISTO_ENABLED)
        {
            true,
        },
#endif

#if 1//def TX_IQ_CAL
        {
            BT_IQ_INVALID_MAGIC_NUM,
            {0},
            {0},
        },
#endif
        // TODO:
        // If want to extend the nvrecord while keeping the history information,
        // append the new items to the tail of NV_EXTENSION_RECORD_T and
        // set their intial content here
    },
};

STATIC_ASSERT(sizeof(local_extension_data) <= NV_EXTENSION_MIRROR_RAM_SIZE, "NV local buffer too small");

static int nv_record_extension_flush(bool is_async);

#ifdef TILE_DATAPATH
static void nvrecord_rebuild_tileconfig(NV_TILE_INFO_CONFIG_T *tileConfig)
{
    memset((uint8_t*)tileConfig,0,sizeof(NV_TILE_INFO_CONFIG_T));
}
#endif

#ifdef OTA_ENABLED
static void nvrecord_rebuild_ota_info(NV_OTA_INFO_T *ota_info)
{
    memset((uint8_t *)ota_info, 0, OTA_DEVICE_CNT * sizeof(NV_OTA_INFO_T));
}
#endif

#if defined(BISTO_ENABLED)
static void nvrecord_rebuild_gsound_info(NV_GSOUND_INFO_T *gsound_info)
{
    memset((uint8_t*)gsound_info,0,sizeof(NV_GSOUND_INFO_T));
}
#endif

#if 1//def TX_IQ_CAL
static void nvrecord_rebuild_btiqcalconfig(BT_IQ_CALIBRATION_CONFIG_T *btIqCalConfig)
{
    memset((uint8_t*)btIqCalConfig,0,sizeof(BT_IQ_CALIBRATION_CONFIG_T));
    btIqCalConfig->validityMagicNum = BT_IQ_INVALID_MAGIC_NUM;
}
#endif

static bool nv_record_data_is_valid(NV_EXTENSION_RECORD_T *nv_record)
{
    bool is_valid = false;

    NVRECORD_HEADER_T* pExtRecInFlash = &nv_record->header;
    uint8_t* pData = (uint8_t*)nv_record + NV_EXTENSION_HEADER_SIZE;

    TRACE(0,"nv ext magic 0x%x valid len %d", pExtRecInFlash->magicNumber,
        pExtRecInFlash->validLen);

    if ((NV_EXTENSION_MAJOR_VERSION == pExtRecInFlash->majorVersion) &&
        (NV_EXTENSION_MAGIC_NUMBER == pExtRecInFlash->magicNumber))
    {
        // check whether the data length is valid
        if (pExtRecInFlash->validLen <= NV_EXTENSION_SIZE-NV_EXTENSION_HEADER_SIZE)
        {
            // check crc32
            uint32_t crc = crc32(0, pData, pExtRecInFlash->validLen);
            TRACE(1,"generated crc32: 0x%x, header crc: 0x%x",
                crc, pExtRecInFlash->crc32);
            if (crc == pExtRecInFlash->crc32)
            {
                // correct
                TRACE(2,"Nv extension is valid.");

                TRACE(2,"Former nv ext valid len %d", pExtRecInFlash->validLen);
                TRACE(2,"Current FW version nv ext valid len %d", NV_EXTENSION_VALID_LEN);

                if (NV_EXTENSION_VALID_LEN < pExtRecInFlash->validLen)
                {
                    TRACE(0,"Valid length of extension must be increased,"
                    "use the default value.");
                }
                else
                {
                    if(NV_EXTENSION_VALID_LEN > pExtRecInFlash->validLen)
                    {
                        TRACE(2, "NV extension is extended! (0x%x) -> (0x%x)",
                              pExtRecInFlash->validLen,NV_EXTENSION_VALID_LEN);
                    }
                    is_valid = true;
                }
            }
        }
    }

    return is_valid;

}

static bool nv_record_items_is_valid(NV_EXTENSION_RECORD_T *nv_record)
{
    nv_record = nv_record;
#if 0
    NV_RECORD_PAIRED_BT_DEV_INFO_T *pbt_pair_info;
    pbt_pair_info = &nv_record->bt_pair_info;
    TRACE(1,"%s: pairedDevNum: %d ", __func__,pbt_pair_info->pairedDevNum);
    for(uint8_t i = 0; i < pbt_pair_info->pairedDevNum; i++)
    {
        DUMP8("0x%x,", (uint8_t*)pbt_pair_info->pairedBtDevInfo[i].record.bdAddr.address,6);
    }
#endif
    // TODO:  add cheking for nv_record items.

    return true;
}

static void nv_record_set_default(NV_EXTENSION_RECORD_T *nv_record)
{
    memset((uint8_t*)nv_record, 0 ,sizeof(NV_EXTENSION_RECORD_T));
    nvrecord_rebuild_system_env(&(nv_record->system_info));
    nvrecord_rebuild_paired_bt_dev_info(&(nv_record->bt_pair_info));
    nvrecord_rebuild_paired_ble_dev_info(&(nv_record->ble_pair_info));
#ifdef GFPS_ENABLED
    nvrecord_rebuild_fp_account_key(&(nv_record->fp_account_key_rec));
#endif

#ifdef NVREC_BAIDU_DATA_SECTION
    nvrecord_rebuild_dma_configuration(&(nv_record->dma_config));
#endif

#ifdef TILE_DATAPATH
    nvrecord_rebuild_tileconfig(&nv_record->tileConfig);
#endif

#if defined(BISTO_ENABLED)
    nvrecord_rebuild_gsound_info(&nv_record->gsound_info);
#endif

#ifdef OTA_ENABLED
    nvrecord_rebuild_ota_info((NV_OTA_INFO_T *)&nv_record->ota_info);
#endif

#if 1//def TX_IQ_CAL
    nvrecord_rebuild_btiqcalconfig(&nv_record->btIqCalConfig);
#endif

    nv_record->header.magicNumber = NV_EXTENSION_MAGIC_NUMBER;
    nv_record->header.majorVersion = NV_EXTENSION_MAJOR_VERSION;
    nv_record->header.minorVersion = NV_EXTENSION_MINOR_VERSION;
    nv_record->header.validLen = NV_EXTENSION_VALID_LEN;
    nv_record->header.crc32 =
        crc32(0, ((uint8_t *)nv_record + NV_EXTENSION_HEADER_SIZE),
                   NV_EXTENSION_VALID_LEN);
}

static void _nv_record_extension_init(void)
{
    enum NORFLASH_API_RET_T result;
    uint32_t sector_size = 0;
    uint32_t block_size = 0;
    uint32_t page_size = 0;

    hal_norflash_get_size(HAL_NORFLASH_ID_0,
               NULL,
               &block_size,
               &sector_size,
               &page_size);
    result = norflash_api_register(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                    HAL_NORFLASH_ID_0,
                    ((uint32_t)__userdata_start),
                    ((uint32_t)__userdata_end - (uint32_t)__userdata_start),
                    block_size,
                    sector_size,
                    page_size,
                    NV_EXTENSION_SIZE*2,
                    nv_extension_callback
                    );
    ASSERT(result == NORFLASH_API_OK,"_nv_record_extension_init: module register failed! result = %d.",result);
}

uint32_t nv_record_pre_write_operation(void)
{
    uint32_t lock = int_lock_global();
    mpu_clear(MPU_ID_USER_DATA_SECTION);
    return lock;
}

void nv_record_post_write_operation(uint32_t lock)
{
    int ret = 0;
    uint32_t nv_start = (uint32_t)&local_extension_data.nv_record;
    uint32_t len = NV_EXTENSION_MIRROR_RAM_SIZE;

    ret = mpu_set(MPU_ID_USER_DATA_SECTION, nv_start, len, 0, MPU_ATTR_READ);
    int_unlock_global(lock);
    TRACE(2,"set mpu 0x%x len %d result %d", nv_start, len, ret);
}

static void nv_record_extension_init(void)
{
    uint32_t lock;
    bool main_is_valid = false;
    bool bak_is_valid = false;
    bool data_is_valid = false;

    if(nvrec_init)
    {
        return;
    }
    _user_data_main_start = (uint32_t)__userdata_start;
    _user_data_bak_start = (uint32_t)__userdata_start + NV_EXTENSION_SIZE;

    lock = nv_record_pre_write_operation();
    _nv_record_extension_init();

    nv_flsh_state.is_update = false;
    nv_flsh_state.written_size = 0;
    nv_flsh_state.state = NV_STATE_IDLE;

    nvrecord_extension_p = &local_extension_data.nv_record;

    if(nvrecord_extension_p->header.magicNumber != NV_EXTENSION_MAGIC_NUMBER ||
       nvrecord_extension_p->header.majorVersion != NV_EXTENSION_MAJOR_VERSION ||
       nvrecord_extension_p->header.minorVersion != NV_EXTENSION_MINOR_VERSION ||
       nvrecord_extension_p->header.validLen >= (NV_EXTENSION_SIZE - NV_EXTENSION_HEADER_SIZE))
    {
        ASSERT(0, "%s: local_extension_data error!(0x%x,0x%x,0x%x,0x%x)",
               __func__,
               nvrecord_extension_p->header.magicNumber,
               nvrecord_extension_p->header.majorVersion,
               nvrecord_extension_p->header.minorVersion,
               nvrecord_extension_p->header.validLen);
    }

    // Check main sector.
    if(nv_record_data_is_valid((NV_EXTENSION_RECORD_T*)_user_data_main_start)
        && nv_record_items_is_valid((NV_EXTENSION_RECORD_T*)_user_data_main_start))
    {
        TRACE(2,"%s,main sector is valid.",__func__);
        main_is_valid = true;
    }
    else
    {
        TRACE(1,"%s,main sector is invalid!",__func__);
        main_is_valid = false;
    }

    // Check bak secotr.
    if(nv_record_data_is_valid((NV_EXTENSION_RECORD_T*)_user_data_bak_start)
       && nv_record_items_is_valid((NV_EXTENSION_RECORD_T*)_user_data_main_start))
    {
        TRACE(2,"%s,bak sector is valid.",__func__);
        bak_is_valid = true;
    }
    else
    {
        TRACE(1,"%s,bak sector is invalid!",__func__);
        bak_is_valid = false;
    }

    if(main_is_valid)
    {
        data_is_valid = true;
        if(!bak_is_valid)
        {
            nv_flsh_state.is_update = true;
            nv_flsh_state.state = NV_STATE_MAIN_DONE;
        }
    }
    else
    {
        if(bak_is_valid)
        {
            data_is_valid = true;
            nv_flsh_state.is_update = true;
            nv_flsh_state.state = NV_STATE_IDLE;
        }
        else
        {
            data_is_valid = false;
        }
    }

    if(data_is_valid)
    {
        TRACE(2,"%s,data is valid.", __func__);
        if(main_is_valid)
        {
            memcpy((uint8_t *)nvrecord_extension_p + NV_EXTENSION_HEADER_SIZE,
                       (uint8_t *)_user_data_main_start + NV_EXTENSION_HEADER_SIZE,
                        NV_EXTENSION_VALID_LEN);
        }
        else
        {
            memcpy((uint8_t *)nvrecord_extension_p + NV_EXTENSION_HEADER_SIZE,
                       (uint8_t *)_user_data_bak_start + NV_EXTENSION_HEADER_SIZE,
                        NV_EXTENSION_VALID_LEN);
        }
        nvrecord_extension_p->header.crc32 =
                    crc32(0,
                          ((uint8_t *)nvrecord_extension_p + NV_EXTENSION_HEADER_SIZE),
                          nvrecord_extension_p->header.validLen);
    }
    else
    {
        TRACE(1,"%s,data invalid, rebuild... ",__func__);
        nv_record_set_default(nvrecord_extension_p);
        TRACE(2,"%s,rebuild crc = 0x%x. ",__func__,nvrecord_extension_p->header.crc32);
        // need to update the content in the flash
        nv_record_extension_update();
    }

    nvrec_init = true;

    nv_record_post_write_operation(lock);

    if(nv_flsh_state.is_update)
    {
        nv_record_extension_flush(false);
    }
    TRACE(2,"%s,done.", __func__);
}

NV_EXTENSION_RECORD_T* nv_record_get_extension_entry_ptr(void)
{
    return nvrecord_extension_p;
}

void nv_record_extension_update(void)
{
    nv_flsh_state.is_update = true;
}

static int nv_record_extension_flush_main(bool is_async)
{
    uint32_t crc;
    uint32_t lock;
    enum NORFLASH_API_RET_T ret = NORFLASH_API_OK;

    if(NULL == nvrecord_extension_p)
    {
        TRACE(1,"%s,nvrecord_extension_p is null.", __func__);
        goto _func_end;
    }

    // TRACE(3, "%s is_async %d state %d", __func__, is_async, nv_flsh_state.state);

    if(is_async)
    {
        if(nv_flsh_state.state == NV_STATE_IDLE
           && nv_flsh_state.is_update == true)
        {
            TRACE(3,"%s: async flush begin!", __func__);
        }

        hal_trace_pause();
        lock = int_lock_global();

        if(nv_flsh_state.state == NV_STATE_IDLE
           || nv_flsh_state.state == NV_STATE_MAIN_ERASING)
        {
            if(nv_flsh_state.state == NV_STATE_IDLE)
            {
                nv_flsh_state.state = NV_STATE_MAIN_ERASING;
                TRACE(4,"%s: NV_STATE_MAIN_ERASING", __func__);
            }

            ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        (uint32_t)(__userdata_start),
                        NV_EXTENSION_SIZE,
                        true);
            if(ret == NORFLASH_API_OK)
            {
                nv_flsh_state.state = NV_STATE_MAIN_ERASED;
                TRACE(4,"%s: NV_STATE_MAIN_ERASED", __func__);
                TRACE(4,"%s: norflash_api_erase ok,addr = 0x%x.",__func__,_user_data_main_start);
            }
            else if(ret == NORFLASH_API_BUFFER_FULL)
            {
                norflash_api_flush();
            }
            else
            {
                ASSERT(0,"%s: norflash_api_erase err,ret = %d,addr = 0x%x.",__func__,ret,_user_data_main_start);
            }
        }
        else if(nv_flsh_state.state == NV_STATE_MAIN_ERASED
               || nv_flsh_state.state == NV_STATE_MAIN_WRITTING)
        {
            if(nv_flsh_state.state == NV_STATE_MAIN_ERASED)
            {
                nv_flsh_state.state = NV_STATE_MAIN_WRITTING;
                TRACE(4,"%s: NV_STATE_MAIN_WRITTING", __func__);
            }
            uint32_t tmpLock = nv_record_pre_write_operation();
            crc = crc32(0,(uint8_t *)nvrecord_extension_p + NV_EXTENSION_HEADER_SIZE,
                 NV_EXTENSION_VALID_LEN);
            nvrecord_extension_p->header.crc32 = crc;
            ASSERT(nv_record_data_is_valid(nvrecord_extension_p)
                   && nv_record_items_is_valid(nvrecord_extension_p),
                  "%s nv_record is invalid!",__func__);
            ret = norflash_api_write(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        (uint32_t)(__userdata_start),
                        (uint8_t *)nvrecord_extension_p,
                        NV_EXTENSION_SIZE,
                        true);
            nv_record_post_write_operation(tmpLock);
            if(ret == NORFLASH_API_OK)
            {
                nv_flsh_state.is_update = false;
                nv_flsh_state.state = NV_STATE_MAIN_WRITTEN;
                TRACE(4,"%s: NV_STATE_MAIN_WRITTEN", __func__);
                TRACE(4,"%s: norflash_api_write ok,addr = 0x%x.",__func__,_user_data_main_start);
            }
            else if(ret == NORFLASH_API_BUFFER_FULL)
            {
                norflash_api_flush();
            }
            else
            {
                ASSERT(0,"%s: norflash_api_write err,ret = %d,addr = 0x%x.",__func__,ret,_user_data_main_start);
            }
        }
        else
        {
            if(norflash_api_get_used_buffer_count(NORFLASH_API_MODULE_ID_USERDATA_EXT,NORFLASH_API_ALL) == 0)
            {
                nv_flsh_state.state = NV_STATE_MAIN_DONE;
                // TRACE(1,"%s: NV_STATE_MAIN_DONE", __func__);
                TRACE(1,"%s: async flush done.", __func__);
            }
            else
            {
                norflash_api_flush();
            }
        }
        int_unlock_global(lock);
        hal_trace_continue();
    }
    else
    {
        TRACE(4,"%s: sync flush begin!", __func__);
        if(nv_flsh_state.state == NV_STATE_IDLE
           || nv_flsh_state.state == NV_STATE_MAIN_ERASING)
        {
            do
            {
                hal_trace_pause();
                lock = int_lock_global();
                ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        (uint32_t)(__userdata_start),
                        NV_EXTENSION_SIZE,
                        true);
                int_unlock_global(lock);
                hal_trace_continue();
                if(ret == NORFLASH_API_OK)
                {
                    nv_flsh_state.state = NV_STATE_MAIN_ERASED;
                    TRACE(4,"%s: norflash_api_erase ok,addr = 0x%x.",__func__, _user_data_main_start);
                }
                else if(ret == NORFLASH_API_BUFFER_FULL)
                {
                    do
                    {
                        norflash_api_flush();
                    }while(norflash_api_get_free_buffer_count(NORFLASH_API_ERASING) == 0);
                }
                else
                {
                    ASSERT(0,"%s: norflash_api_erase err,ret = %d,addr = 0x%x.",__func__,ret,_user_data_main_start);
                }

            }while(ret == NORFLASH_API_BUFFER_FULL);
        }

        if(nv_flsh_state.state == NV_STATE_MAIN_ERASED
           || nv_flsh_state.state == NV_STATE_MAIN_WRITTING)
        {
            do
            {
                hal_trace_pause();
                uint32_t tmpLock = nv_record_pre_write_operation();
                crc = crc32(0,(uint8_t *)nvrecord_extension_p + NV_EXTENSION_HEADER_SIZE,
                     NV_EXTENSION_VALID_LEN);
                nvrecord_extension_p->header.crc32 = crc;
                ASSERT(nv_record_data_is_valid(nvrecord_extension_p)
                       && nv_record_items_is_valid(nvrecord_extension_p),
                        "%s nv_record is invalid!",__func__);
                ret = norflash_api_write(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        (uint32_t)(__userdata_start),
                        (uint8_t *)nvrecord_extension_p,
                        NV_EXTENSION_SIZE,
                        true);
                nv_record_post_write_operation(tmpLock);
                hal_trace_continue();
                if(ret == NORFLASH_API_OK)
                {
                    nv_flsh_state.is_update = false;
                    nv_flsh_state.state = NV_STATE_MAIN_WRITTEN;
                    TRACE(4,"%s: norflash_api_write ok,addr = 0x%x.",__func__,_user_data_main_start);
                }
                else if(ret == NORFLASH_API_BUFFER_FULL)
                {
                    do
                    {
                        norflash_api_flush();
                    }while(norflash_api_get_free_buffer_count(NORFLASH_API_WRITTING) == 0);
                }
                else
                {
                    ASSERT(0,"%s: norflash_api_write err,ret = %d,addr = 0x%x.",__func__,ret,_user_data_main_start);
                }
            }while(ret == NORFLASH_API_BUFFER_FULL);
        }

            do
            {
                norflash_api_flush();
            }while(norflash_api_get_used_buffer_count(NORFLASH_API_MODULE_ID_USERDATA_EXT,NORFLASH_API_ALL) > 0);

            nv_flsh_state.state = NV_STATE_MAIN_DONE;
        	TRACE(1,"%s: sync flush done.", __func__);
    }
_func_end:
    return (ret == NORFLASH_API_OK) ? 0:1;
}

static int nv_record_extension_flush_bak(bool is_async)
{
    uint32_t lock;
    enum NORFLASH_API_RET_T ret = NORFLASH_API_OK;
    uint8_t *burn_buf = (uint8_t*)_nv_burn_buf;

    if(is_async)
    {
        hal_trace_pause();
        lock = int_lock_global();
        if(nv_flsh_state.state == NV_STATE_MAIN_DONE
           || nv_flsh_state.state == NV_STATE_BAK_ERASING)
        {
            if(nv_flsh_state.state == NV_STATE_MAIN_DONE)
            {
                nv_flsh_state.state = NV_STATE_BAK_ERASING;
                TRACE(3,"%s: async flush begin", __func__);
            }
            ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                    _user_data_bak_start,
                    NV_EXTENSION_SIZE,
                    true);
            if(ret == NORFLASH_API_OK)
            {
                nv_flsh_state.state = NV_STATE_BAK_ERASED;
                TRACE(4,"%s: norflash_api_erase ok,addr = 0x%x.",
                        __func__,_user_data_bak_start);
                // TRACE(4,"%s: NV_STATE_BAK_ERASED", __func__);
            }
            else if(ret == NORFLASH_API_BUFFER_FULL)
            {
                norflash_api_flush();
            }
            else
            {
                ASSERT(0,"%s: norflash_api_erase err,ret = %d,addr = 0x%x.",
                        __func__,ret,_user_data_bak_start);
            }
        }
        else if(nv_flsh_state.state == NV_STATE_BAK_ERASED
           || nv_flsh_state.state == NV_STATE_BAK_WRITTING)
        {
            if(nv_flsh_state.state == NV_STATE_BAK_ERASED)
            {
                nv_flsh_state.state = NV_STATE_BAK_WRITTING;
                nv_flsh_state.written_size = 0;
                // TRACE(4,"%s: NV_STATE_BAK_WRITTING", __func__);
            }
            do
            {
                ret = norflash_api_read(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        _user_data_main_start + nv_flsh_state.written_size,
                        burn_buf,
                        NV_EXTENSION_PAGE_SIZE
                        );
                ASSERT(ret == NORFLASH_API_OK,"norflash_api_read failed! ret = %d, addr = 0x%x.",
                        (int32_t)ret,_user_data_main_start + nv_flsh_state.written_size);

                ret = norflash_api_write(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        _user_data_bak_start + nv_flsh_state.written_size,
                        burn_buf,
                        NV_EXTENSION_PAGE_SIZE,
                        true
                        );
                if(ret == NORFLASH_API_OK)
                {
                    nv_flsh_state.written_size += NV_EXTENSION_PAGE_SIZE;

                    if(nv_flsh_state.written_size == NV_EXTENSION_SIZE)
                    {
                        nv_flsh_state.state = NV_STATE_BAK_WRITTEN;
                        // TRACE(4,"%s: NV_STATE_BAK_WRITTEN", __func__);
                        break;
                    }
                }
                else if(ret == NORFLASH_API_BUFFER_FULL)
                {
                    norflash_api_flush();
                    break;
                }
                else
                {
                    ASSERT(0,"%s: norflash_api_write err,ret = %d,addr = 0x%x.",
                        __func__,ret,_user_data_bak_start + nv_flsh_state.written_size);
                }
            }while(1);
        }
        else
        {
            if(norflash_api_get_used_buffer_count(NORFLASH_API_MODULE_ID_USERDATA_EXT,NORFLASH_API_ALL) == 0)
            {
                nv_flsh_state.state = NV_STATE_BAK_DONE;
                TRACE(3,"%s: async flush done.", __func__);
            }
            else
            {
                norflash_api_flush();
            }
        }
        int_unlock_global(lock);
        hal_trace_continue();
    }
    else
    {
        TRACE(4,"%s: sync flush begin.", __func__);
        if(nv_flsh_state.state == NV_STATE_MAIN_DONE
           || nv_flsh_state.state == NV_STATE_BAK_ERASING)
        {
            do
            {
                hal_trace_pause();
                lock = int_lock_global();
                ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                            _user_data_bak_start,
                            NV_EXTENSION_SIZE,
                            true);
                int_unlock_global(lock);
                hal_trace_continue();
                if(ret == NORFLASH_API_OK)
                {
                    nv_flsh_state.state = NV_STATE_BAK_ERASED;
                    TRACE(4,"%s: norflash_api_erase ok,addr = 0x%x.",
                        __func__, _user_data_bak_start);
                }
                else if(ret == NORFLASH_API_BUFFER_FULL)
                {
                    do
                    {
                        norflash_api_flush();
                    }while(norflash_api_get_free_buffer_count(NORFLASH_API_ERASING) == 0);
                }
                else
                {
                    ASSERT(0,"%s: norflash_api_erase err,ret = %d,addr = 0x%x.",__func__,ret,_user_data_bak_start);
                }
            }while(ret == NORFLASH_API_BUFFER_FULL);
        }

        if(nv_flsh_state.state == NV_STATE_BAK_ERASED
           || nv_flsh_state.state == NV_STATE_BAK_WRITTING)
        {
            nv_flsh_state.state = NV_STATE_BAK_WRITTING;
            nv_flsh_state.written_size = 0;

            hal_trace_pause();
            do
            {
                lock = int_lock_global();
                ret = norflash_api_read(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        _user_data_main_start + nv_flsh_state.written_size,
                        burn_buf,
                        NV_EXTENSION_PAGE_SIZE
                        );
                ASSERT(ret == NORFLASH_API_OK,"norflash_api_read failed! ret = %d, addr = 0x%x.",
                        (int32_t)ret, _user_data_main_start + nv_flsh_state.written_size);

                ret = norflash_api_write(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                        _user_data_bak_start + nv_flsh_state.written_size,
                        burn_buf,
                        NV_EXTENSION_PAGE_SIZE,
                        true
                        );
                int_unlock_global(lock);
                if(ret == NORFLASH_API_OK)
                {
                    nv_flsh_state.written_size += NV_EXTENSION_PAGE_SIZE;
                    if(nv_flsh_state.written_size == NV_EXTENSION_SIZE)
                    {
                        nv_flsh_state.state = NV_STATE_BAK_WRITTEN;
                        TRACE(3,"%s: NV_STATE_BAK_WRITTEN", __func__);
                        break;
                    }
                }
                else if(ret == NORFLASH_API_BUFFER_FULL)
                {
                    do
                    {
                        norflash_api_flush();
                    }while(norflash_api_get_free_buffer_count(NORFLASH_API_WRITTING) == 0);
                }
                else
                {
                    ASSERT(0,"%s: norflash_api_write err,ret = %d,addr = 0x%x.",
                        __func__,ret,_user_data_bak_start + nv_flsh_state.written_size);
                }
            }while(1);
        }
        hal_trace_continue();

        // nv_flsh_state.state == NV_STATE_BAK_WRITTEN;
        do
        {
            norflash_api_flush();
        }while(norflash_api_get_used_buffer_count(NORFLASH_API_MODULE_ID_USERDATA_EXT,NORFLASH_API_ALL) > 0);

        nv_flsh_state.state = NV_STATE_BAK_DONE;
        TRACE(1,"%s: sync flush done.", __func__);
    }

    if(nv_flsh_state.state == NV_STATE_BAK_DONE)
    {
       nv_flsh_state.state = NV_STATE_IDLE;
       TRACE(1,"%s: NV_STATE_IDLE", __func__);
    }

    return (ret == NORFLASH_API_OK) ? 0:1;
}


static int nv_record_extension_flush(bool is_async)
{
    int ret = 0;
    // TRACE(3, "%s state %d is_update %d", __func__, nv_flsh_state.state, nv_flsh_state.is_update);
    do{
        if(nv_flsh_state.state == NV_STATE_IDLE
           && nv_flsh_state.is_update == FALSE)
        {
            break;
        }

        if((nv_flsh_state.state == NV_STATE_IDLE
               && nv_flsh_state.is_update == TRUE)
            || nv_flsh_state.state == NV_STATE_MAIN_ERASING
            || nv_flsh_state.state == NV_STATE_MAIN_ERASED
            || nv_flsh_state.state == NV_STATE_MAIN_WRITTING
            || nv_flsh_state.state == NV_STATE_MAIN_WRITTEN
        )
        {
            ret = nv_record_extension_flush_main(is_async);
            if(is_async)
            {
                break;
            }
        }

        if(nv_flsh_state.state == NV_STATE_MAIN_DONE
            || nv_flsh_state.state == NV_STATE_BAK_ERASING
            || nv_flsh_state.state == NV_STATE_BAK_ERASED
            || nv_flsh_state.state == NV_STATE_BAK_WRITTING
            || nv_flsh_state.state == NV_STATE_BAK_WRITTEN
            || nv_flsh_state.state == NV_STATE_BAK_DONE
        )
        {
            ret = nv_record_extension_flush_bak(is_async);
        }
    }while(!is_async);
    return ret;
}

void nv_extension_callback(void* param)
{
    NORFLASH_API_OPERA_RESULT *opera_result;
    opera_result = (NORFLASH_API_OPERA_RESULT*)param;

    TRACE(6,"%s:type = %d, addr = 0x%x,len = 0x%x,remain = %d,result = %d.",
                __func__,
                opera_result->type,
                opera_result->addr,
                opera_result->len,
                opera_result->remain_num,
                opera_result->result);
}

void nv_record_init(void)
{
    nv_record_open(section_usrdata_ddbrecord);

    nv_custom_parameter_section_init();
}

bt_status_t nv_record_open(SECTIONS_ADP_ENUM section_id)
{
    nv_record_extension_init();
#ifdef FLASH_SUSPEND
    hal_sleep_set_sleep_hook(HAL_SLEEP_HOOK_USER_NVRECORD,
                nv_record_flash_flush_in_sleep);
#else
    hal_sleep_set_deep_sleep_hook(HAL_DEEP_SLEEP_HOOK_USER_NVRECORD,
                nv_record_flash_flush_in_sleep);
#endif
    return BT_STS_SUCCESS;
}

void nv_record_update_runtime_userdata(void)
{
    nv_record_extension_update();
}


int nv_record_touch_cause_flush(void)
{
    nv_record_update_runtime_userdata();
    return 0;
}

void nv_record_sector_clear(void)
{
    uint32_t lock;
    enum NORFLASH_API_RET_T ret;

    lock = int_lock_global();
    ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                          (uint32_t)__userdata_start,
                          NV_EXTENSION_SIZE,
                          false);
    ASSERT(ret == NORFLASH_API_OK,
        "%s: norflash_api_erase(0x%x) failed! ret = %d.",
        __func__, (uint32_t)__userdata_start, (int32_t)ret);

    ret = norflash_api_erase(NORFLASH_API_MODULE_ID_USERDATA_EXT,
                          (uint32_t)__userdata_start + NV_EXTENSION_SIZE,
                          NV_EXTENSION_SIZE,
                          false);
    ASSERT(ret == NORFLASH_API_OK,
        "%s: norflash_api_erase(0x%x) failed! ret = %d.",
        __func__, (uint32_t)__userdata_start + NV_EXTENSION_SIZE, (int32_t)ret);
    //pmu_reboot();
    int_unlock_global(lock);
}

void nv_record_rebuild(void)
{
    if(nvrecord_extension_p)
    {
        uint32_t nv_start;
        TRACE(1,"%s: begin.", __func__);
        uint32_t lock = int_lock_global();
        mpu_clear(MPU_ID_USER_DATA_SECTION);
        nv_record_set_default(nvrecord_extension_p);
        nv_start = (uint32_t)&local_extension_data.nv_record;
        mpu_set(MPU_ID_USER_DATA_SECTION, nv_start,
                    NV_EXTENSION_MIRROR_RAM_SIZE, 0, MPU_ATTR_READ);
        nv_record_extension_update();
        nv_record_flash_flush();
        int_unlock_global(lock);
        TRACE(1,"%s: done.", __func__);
    }
    else
    {
        TRACE(1,"%s: begin. nvrecord_extension_p = null.", __func__);
        uint32_t lock = int_lock_global();
        nv_record_sector_clear();
        nvrec_init = false;
        nv_record_extension_init();
        int_unlock_global(lock);
        TRACE(1,"%s: done.", __func__);
    }
}

#define NV_RECORD_FLUSH_EXECUTION_INTERVAL_MS  (20*60*1000)
static uint32_t lastFlushCheckPointInMs = 0;

static void nv_record_reset_flush_checkpoint(void)
{
    lastFlushCheckPointInMs = GET_CURRENT_MS();;
}

static bool nv_record_is_timer_expired_to_check(void)
{
    uint32_t passedTimerMs;
    uint32_t currentTimerMs = GET_CURRENT_MS();

    if (0 == lastFlushCheckPointInMs)
    {
        passedTimerMs = currentTimerMs;
    }
    else
    {
        if (currentTimerMs > lastFlushCheckPointInMs)
        {
            passedTimerMs = currentTimerMs - lastFlushCheckPointInMs;
        }
        else
        {
            passedTimerMs = 0xFFFFFFFF - lastFlushCheckPointInMs + currentTimerMs + 1;
        }
    }

    if (passedTimerMs > NV_RECORD_FLUSH_EXECUTION_INTERVAL_MS)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void nv_record_flash_flush(void)
{
    nv_record_extension_flush(false);
    nv_record_reset_flush_checkpoint();
}

void nv_record_execute_async_flush(void)
{
    int ret = nv_record_extension_flush(true);
    if (0 == ret)
    {
        nv_record_reset_flush_checkpoint();
    }
}

int nv_record_flash_flush_in_sleep(void)
{
    if ((NV_STATE_IDLE == nv_flsh_state.state) &&
        !nv_record_is_timer_expired_to_check())
    {
        return 0;
    }

    nv_record_execute_async_flush();
    return 0;
}

#endif // #if defined(NEW_NV_RECORD_ENABLED)
