/*
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "bcache.h"
#include "assert.h"
#include "stdlib.h"
#include "linux/delay.h"
#include "disk_pri.h"
#include "user_copy.h"

#undef HALARC_ALIGNMENT
#define DMA_ALLGN          64
#define HALARC_ALIGNMENT   DMA_ALLGN
#define BCACHE_MAGIC_NUM   20132016
#define BCACHE_STATCK_SIZE 0x3000
#define ASYNC_EVENT_BIT    0x01

#ifdef DEBUG
#define D(args) printf args
#else
#define D(args)
#endif

#ifdef BCACHE_ANALYSE
UINT32 g_memSize;
volatile UINT32 g_blockNum;
volatile UINT32 g_dataSize;
volatile UINT8 *g_memStart;
volatile UINT32 g_switchTimes[CONFIG_FS_FAT_BLOCK_NUMS] = { 0 };
volatile UINT32 g_hitTimes[CONFIG_FS_FAT_BLOCK_NUMS] = { 0 };
#endif

VOID BcacheAnalyse(UINT32 level)
{
    (VOID)level;
#ifdef BCACHE_ANALYSE
    int i;

    PRINTK("Bcache information:\n");
    PRINTK("    mem: %u\n", g_memSize);
    PRINTK("    block number: %u\n", g_blockNum);
    PRINTK("index, switch, hit\n");
    for (i = 0; i < g_blockNum; i++) {
        PRINTK("%5d, %6d, %3d\n", i, g_switchTimes[i], g_hitTimes[i]);
    }
#else
    PRINTK("Bcache hasn't started\n");
#endif
}

#ifdef LOSCFG_FS_FAT_CACHE_SYNC_THREAD

UINT32 g_syncThreadPrio = CONFIG_FS_FAT_SYNC_THREAD_PRIO;
UINT32 g_dirtyRatio = CONFIG_FS_FAT_DIRTY_RATIO;
UINT32 g_syncInterval = CONFIG_FS_FAT_SYNC_INTERVAL;

VOID LOS_SetDirtyRatioThreshold(UINT32 dirtyRatio)
{
    if ((dirtyRatio != g_dirtyRatio) && (dirtyRatio <= 100)) { /* The ratio cannot exceed 100% */
        g_dirtyRatio = dirtyRatio;
    }
}

VOID LOS_SetSyncThreadInterval(UINT32 interval)
{
    g_syncInterval = interval;
}

INT32 LOS_SetSyncThreadPrio(UINT32 prio, const CHAR *name)
{
    INT32 ret = VFS_ERROR;
    INT32 diskID;
    los_disk *disk = NULL;
    if ((prio == 0) || (prio >= OS_TASK_PRIORITY_LOWEST)) { /* The priority can not be zero */
        return ret;
    }

    g_syncThreadPrio = prio;

    /*
     * If the name is NULL, it only sets the value of a global variable,
     * and takes effect the next time the thread is created.
     */
    if (name == NULL) {
        return ENOERR;
    }

    /* If the name is not NULL, it shall return an error if can't find the disk corresponding to name. */
    diskID = los_get_diskid_byname(name);
    disk = get_disk(diskID);
    if (disk == NULL) {
        return ret;
    }

    if (pthread_mutex_lock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex lock fail!\n", __FUNCTION__, __LINE__);
        return ret;
    }
    if ((disk->disk_status == STAT_INUSED) && (disk->bcache != NULL)) {
        ret = LOS_TaskPriSet(disk->bcache->syncTaskId, prio);
    }
    if (pthread_mutex_unlock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex unlock fail!\n", __FUNCTION__, __LINE__);
        return VFS_ERROR;
    }
    return ret;
}
#endif

static OsBcacheBlock *RbFindBlock(const OsBcache *bc, UINT64 num)
{
    OsBcacheBlock *block = NULL;
    struct rb_node *node = bc->rbRoot.rb_node;

    for (; node != NULL; node = (block->num < num) ? node->rb_right : node->rb_left) {
        block = rb_entry(node, OsBcacheBlock, rbNode);
        if (block->num == num) {
            return block;
        }
    }
    return NULL;
}

static VOID RbAddBlock(OsBcache *bc, OsBcacheBlock *block)
{
    struct rb_node *node = bc->rbRoot.rb_node;
    struct rb_node **link = NULL;
    OsBcacheBlock *b = NULL;

    if (node == NULL) {
        rb_link_node(&block->rbNode, NULL, &bc->rbRoot.rb_node);
    } else {
        for (; node != NULL; link = (b->num > block->num) ? &node->rb_left : &node->rb_right, node = *link) {
            b = rb_entry(node, OsBcacheBlock, rbNode);
            if (b->num == block->num) {
                PRINT_ERR("RbAddBlock fail, b->num = %llu, block->num = %llu\n", b->num, block->num);
                return;
            }
        }
        rb_link_node(&block->rbNode, &b->rbNode, link);
    }
    rb_insert_color(&block->rbNode, &bc->rbRoot);
}

static inline VOID RbDelBlock(OsBcache *bc, OsBcacheBlock *block)
{
    rb_erase(&block->rbNode, &bc->rbRoot);
}

static inline VOID ListMoveBlockToHead(OsBcache *bc, OsBcacheBlock *block)
{
    LOS_ListDelete(&block->listNode);
    LOS_ListAdd(&bc->listHead, &block->listNode);
}

static inline VOID FreeBlock(OsBcache *bc, OsBcacheBlock *block)
{
    block->used = FALSE;
    LOS_ListAdd(&bc->freeListHead, &block->listNode);
}

static UINT32 GetValLog2(UINT32 val)
{
    UINT32 i, log2;

    i = val;
    log2 = 0;
    while ((i & 1) == 0) { /* Check if the last bit is 1 */
        i >>= 1;
        log2++;
    }
    if (i != 1) { /* Not the power of 2 */
        return 0;
    }

    return log2;
}

static INT32 FindFlagPos(const UINT32 *arr, UINT32 len, UINT32 *p1, UINT32 *p2)
{
    UINT32 *start = p1;
    UINT32 *end = p2;
    UINT32 i, j, tmp;
    UINT32 val = 1;

    *start = BCACHE_MAGIC_NUM;
    *end = 0;
    for (i = 0; i < len; i++) {
        for (j = 0; j < UNSIGNED_INTEGER_BITS; j++) {
            tmp = arr[i] << j;
            tmp = tmp >> UNINT_MAX_SHIFT_BITS;
            if (tmp != val) {
                continue;
            }
            if (val && (*start == BCACHE_MAGIC_NUM)) {
                *start = (i << UNINT_LOG2_SHIFT) + j;
                val = 1 - val; /* Control parity by 0 and 1 */
            } else if (val && (*start != BCACHE_MAGIC_NUM)) {
                *start = 0;
                return VFS_ERROR;
            } else {
                *end = (i << UNINT_LOG2_SHIFT) + j;
                val = 1 - val; /* Control parity by 0 and 1 */
            }
        }
    }
    if (*start == BCACHE_MAGIC_NUM) {
        *start = 0;
        return VFS_ERROR;
    }
    if (*end == 0) {
        *end = len << UNINT_LOG2_SHIFT;
    }

    return ENOERR;
}

static INT32 BlockRead(OsBcache *bc, OsBcacheBlock *block, UINT8 *buf)
{
    INT32 ret = bc->breadFun(bc->priv, buf, bc->sectorPerBlock,
                             (block->num) << GetValLog2(bc->sectorPerBlock));
    if (ret) {
        PRINT_ERR("BlockRead, brread_fn error, ret = %d\n", ret);
        if (block->modified == FALSE) {
            if (block->listNode.pstNext != NULL) {
                LOS_ListDelete(&block->listNode); /* list del block */
                RbDelBlock(bc, block);
            }
            FreeBlock(bc, block);
        }
        return ret;
    }

    block->readFlag = TRUE;
    return ENOERR;
}

static INT32 BcacheGetFlag(OsBcache *bc, OsBcacheBlock *block)
{
    UINT32 i, n, f, sectorPos, val, start, pos, currentSize;
    UINT32 flagUse = bc->sectorPerBlock >> UNINT_LOG2_SHIFT;
    UINT32 flag = UINT_MAX;
    INT32 ret, bits;

    if (block->readFlag == TRUE) {
        return ENOERR;
    }

    for (i = 0; i < flagUse; i++) {
        flag &= block->flag[i];
    }

    if (flag == UINT_MAX) {
        return ENOERR;
    }

    ret = BlockRead(bc, block, bc->rwBuffer);
    if (ret != ENOERR) {
        return ret;
    }

    for (i = 0, sectorPos = 0; i < flagUse; i++) {
        val = block->flag[i];
        /* use unsigned integer for bit map */
        for (f = 0, bits = UNSIGNED_INTEGER_BITS; bits > 0; val = ~(val << n), f++, bits = bits - (INT32)n) {
            if (val == 0) {
                n = UNSIGNED_INTEGER_BITS;
            } else {
                n = (UINT32)CLZ(val);
            }
            sectorPos += n;
            if (((f % EVEN_JUDGED) != 0) || (n == 0)) { /* Number of leading zeros of n is zero */
                goto LOOP;
            }
            if (sectorPos > ((i + 1) << UNINT_LOG2_SHIFT)) {
                start = sectorPos - n;
                currentSize = (((i + 1) << UNINT_LOG2_SHIFT) - start) * bc->sectorSize;
            } else {
                start = sectorPos - n;
                currentSize = n * bc->sectorSize;
            }
            pos = start * bc->sectorSize;
            if (memcpy_s(block->data + pos, bc->blockSize - pos, bc->rwBuffer + pos, currentSize) != EOK) {
                return VFS_ERROR;
            }
LOOP:
            if (sectorPos > ((i + 1) << UNINT_LOG2_SHIFT)) {
                sectorPos = (i + 1) << UNINT_LOG2_SHIFT;
            }
        }
    }

    return ENOERR;
}

static VOID BcacheSetFlag(const OsBcache *bc, OsBcacheBlock *block, UINT32 pos, UINT32 size)
{
    UINT32 start, num, i, j, k;

    if (bc->sectorSize == 0) {
        PRINT_ERR("BcacheSetFlag sectorSize is equal to zero! \n");
        return;
    }

    start = pos / bc->sectorSize;
    num = size / bc->sectorSize;

    i = start / UNSIGNED_INTEGER_BITS;
    j = start % UNSIGNED_INTEGER_BITS;
    for (k = 0; k < num; k++) {
        block->flag[i] |= 1u << (UNINT_MAX_SHIFT_BITS - j);
        j++;
        if (j == UNSIGNED_INTEGER_BITS) {
            j = 0;
            i++;
        }
    }
}

static INT32 BcacheSyncBlock(OsBcache *bc, OsBcacheBlock *block)
{
    INT32 ret = ENOERR;
    UINT32 len, start, end;

    if (block->modified == TRUE) {
        D(("bcache writting block = %llu\n", block->num));

        ret = FindFlagPos(block->flag, bc->sectorPerBlock >> UNINT_LOG2_SHIFT, &start, &end);
        if (ret == ENOERR) {
            len = end - start;
        } else {
            ret = BcacheGetFlag(bc, block);
            if (ret != ENOERR) {
                return ret;
            }

            len = bc->sectorPerBlock;
        }

        ret = bc->bwriteFun(bc->priv, (const UINT8 *)(block->data + (start * bc->sectorSize)),
                            len, (block->num * bc->sectorPerBlock) + start);
        if (ret == ENOERR) {
            block->modified = FALSE;
            bc->modifiedBlock--;
        } else {
            PRINT_ERR("BcacheSyncBlock fail, ret = %d, len = %u, block->num = %llu, start = %u\n",
                      ret, len, block->num, start);
        }
    }
    return ret;
}

static void NumListAdd(OsBcache *bc, OsBcacheBlock *block)
{
    OsBcacheBlock *temp = NULL;

    LOS_DL_LIST_FOR_EACH_ENTRY(temp, &bc->numHead, OsBcacheBlock, numNode) {
        if (temp->num > block->num) {
            LOS_ListTailInsert(&temp->numNode, &block->numNode);
            return;
        }
    }

    LOS_ListTailInsert(&bc->numHead, &block->numNode);
}

static void AddBlock(OsBcache *bc, OsBcacheBlock *block)
{
    RbAddBlock(bc, block);
    NumListAdd(bc, block);
    bc->sumNum += block->num;
    bc->nBlock++;
    LOS_ListAdd(&bc->listHead, &block->listNode);
}

static void DelBlock(OsBcache *bc, OsBcacheBlock *block)
{
    LOS_ListDelete(&block->listNode); /* lru list del */
    LOS_ListDelete(&block->numNode);  /* num list del */
    bc->sumNum -= block->num;
    bc->nBlock--;
    RbDelBlock(bc, block);            /* rb  tree del */
    FreeBlock(bc, block);             /* free list add */
}

static BOOL BlockAllDirty(const OsBcache *bc, OsBcacheBlock *block)
{
    UINT32 start = 0;
    UINT32 end = 0;
    UINT32 len = bc->sectorPerBlock >> UNINT_LOG2_SHIFT;

    if (block->modified == TRUE) {
        if (block->allDirty) {
            return TRUE;
        }

        if (FindFlagPos(block->flag, len, &start, &end) == ENOERR) {
            if ((end - start) == bc->sectorPerBlock) {
                block->allDirty = TRUE;
                return TRUE;
            }
        }
    }

    return FALSE;
}

static OsBcacheBlock *GetBaseBlock(OsBcache *bc)
{
    OsBcacheBlock *base = bc->wStart;
    OsBcacheBlock *end = bc->wEnd;
    while (base < end) {
        if (base->used == FALSE) {
            base->used = TRUE;
            LOS_ListDelete(&base->listNode);
            return base;
        }
        base++;
    }

    return NULL;
}

/* try get free block first, if failed free a useless block */
static OsBcacheBlock *GetSlowBlock(OsBcache *bc, BOOL read)
{
    LOS_DL_LIST *node = NULL;
    OsBcacheBlock *block = NULL;

    LOS_DL_LIST_FOR_EACH_ENTRY(block, &bc->freeListHead, OsBcacheBlock, listNode) {
        if (block->readBuff == read) {
            block->used = TRUE;
            LOS_ListDelete(&block->listNode);
            return block; /* get free one */
        }
    }

    node = bc->listHead.pstPrev;
    while (node != &bc->listHead) {
        block = LOS_DL_LIST_ENTRY(node, OsBcacheBlock, listNode);
        node = block->listNode.pstPrev;

        if (block->readBuff == read) {
            if (block->modified == TRUE) {
                BcacheSyncBlock(bc, block);
            }

            DelBlock(bc, block);
            block->used = TRUE;
            LOS_ListDelete(&block->listNode);
            return block; /* get used one */
        }
    }

    return NULL;
}

/* flush combined blocks */
static VOID WriteMergedBlocks(OsBcache *bc, OsBcacheBlock *begin, int blocks)
{
    INT32 ret;
    OsBcacheBlock *cur = NULL;
    OsBcacheBlock *next = NULL;
    UINT32 len = blocks * bc->sectorPerBlock;
    UINT64 pos = begin->num * bc->sectorPerBlock;

    ret = bc->bwriteFun(bc->priv, (const UINT8 *)begin->data, len, pos);
    if (ret != ENOERR) {
        PRINT_ERR("WriteMergedBlocks bwriteFun failed ret %d\n", ret);
        return;
    }

    bc->modifiedBlock -= blocks;
    cur = begin;
    while (blocks > 0) {
        next = LOS_DL_LIST_ENTRY(cur->numNode.pstNext, OsBcacheBlock, numNode);
        DelBlock(bc, cur);
        cur->modified = FALSE;
        blocks--;
        cur = next;
    }
}

/* find continue blocks and flush them */
static VOID MergeSyncBlocks(OsBcache *bc, OsBcacheBlock *start)
{
    INT32 mergedBlock = 0;
    OsBcacheBlock *cur = start;
    OsBcacheBlock *last = NULL;

    while (cur <= bc->wEnd) {
        if (!cur->used || !BlockAllDirty(bc, cur)) {
            break;
        }

        if (last && (last->num + 1 != cur->num)) {
            break;
        }

        mergedBlock++;
        last = cur;
        cur++;
    }

    if (mergedBlock > 0) {
        WriteMergedBlocks(bc, start, mergedBlock);
    }
}

/* get the min write block num of block cache buffer */
static inline UINT64 GetMinWriteNum(OsBcache *bc)
{
    UINT64 ret = 0;
    OsBcacheBlock *block = NULL;

    LOS_DL_LIST_FOR_EACH_ENTRY(block, &bc->numHead, OsBcacheBlock, numNode) {
        if (!block->readBuff) {
            ret = block->num;
            break;
        }
    }

    return ret;
}

static OsBcacheBlock *AllocNewBlock(OsBcache *bc, BOOL read, UINT64 num)
{
    OsBcacheBlock *last = NULL;
    OsBcacheBlock *prefer = NULL;

    if (read) { /* read */
        return GetSlowBlock(bc, TRUE);
    }

    /* fallback, this may happen when the block previously flushed, use read buffer */
    if (bc->nBlock && num < GetMinWriteNum(bc)) {
        return GetSlowBlock(bc, TRUE);
    }

    last = RbFindBlock(bc, num - 1);  /* num=0 is ok */
    if (last == NULL || last->readBuff) {
        return GetBaseBlock(bc);      /* new block */
    }

    prefer = last + 1;
    if (prefer > bc->wEnd) {
        prefer = bc->wStart;
    }

    /* this is a sync thread synced block! */
    if (prefer->used && !prefer->modified) {
        prefer->used = FALSE;
        DelBlock(bc, prefer);
    }

    if (prefer->used) {
        MergeSyncBlocks(bc, prefer);
    }

    if (prefer->used) {
        BcacheSyncBlock(bc, prefer);
        DelBlock(bc, prefer);
    }

    prefer->used = TRUE;
    LOS_ListDelete(&prefer->listNode); /* del from free list */

    return prefer;
}

static INT32 BcacheSync(OsBcache *bc)
{
    LOS_DL_LIST *node = NULL;
    OsBcacheBlock *block = NULL;
    INT32 ret = ENOERR;

    D(("bcache cache sync\n"));

    (VOID)pthread_mutex_lock(&bc->bcacheMutex);
    node = bc->listHead.pstPrev;
    while (&bc->listHead != node) {
        block = LOS_DL_LIST_ENTRY(node, OsBcacheBlock, listNode);
        ret = BcacheSyncBlock(bc, block);
        if (ret != ENOERR) {
            PRINT_ERR("BcacheSync error, ret = %d\n", ret);
            break;
        }
        node = node->pstPrev;
    }
    (VOID)pthread_mutex_unlock(&bc->bcacheMutex);

    return ret;
}

static VOID BlockInit(OsBcache *bc, OsBcacheBlock *block, UINT64 num)
{
    (VOID)memset_s(block->flag, sizeof(block->flag), 0, sizeof(block->flag));
    block->num = num;
    block->readFlag = FALSE;
    if (block->modified == TRUE) {
        block->modified = FALSE;
        bc->modifiedBlock--;
    }
    block->allDirty = FALSE;
}

static INT32 BcacheGetBlock(OsBcache *bc, UINT64 num, BOOL readData, OsBcacheBlock **dblock)
{
    INT32 ret;
    OsBcacheBlock *block = NULL;
    OsBcacheBlock *first = NULL;

    /*
     * First check if the most recently used block is the requested block,
     * this can improve performance when using byte access functions.
     */
    if (LOS_ListEmpty(&bc->listHead) == FALSE) {
        first = LOS_DL_LIST_ENTRY(bc->listHead.pstNext, OsBcacheBlock, listNode);
        block = (first->num == num) ? first : RbFindBlock(bc, num);
    }

    if (block != NULL) {
        D(("bcache block = %llu found in cache\n", num));
#ifdef BCACHE_ANALYSE
        UINT32 index = ((UINT32)(block->data - g_memStart)) / g_dataSize;
        PRINTK(", [HIT], %llu, %u\n", num, index);
        g_hitTimes[index]++;
#endif

        if (first != block) {
            ListMoveBlockToHead(bc, block);
        }
        *dblock = block;

        if ((bc->prereadFun != NULL) && (readData == TRUE) && (block->pgHit == 1)) {
            block->pgHit = 0;
            bc->prereadFun(bc, block);
        }

        return ENOERR;
    }

    D(("bcache block = %llu NOT found in cache\n", num));

    block = AllocNewBlock(bc, readData, num);
    if (block == NULL) {
        block = GetSlowBlock(bc, readData);
    }

    if (block == NULL) {
        return -ENOMEM;
    }
#ifdef BCACHE_ANALYSE
    UINT32 index = ((UINT32)(block->data - g_memStart)) / g_dataSize;
    PRINTK(", [MISS], %llu, %u\n", num, index);
    g_switchTimes[index]++;
#endif
    BlockInit(bc, block, num);

    if (readData == TRUE) {
        D(("bcache reading block = %llu\n", block->num));

        ret = BlockRead(bc, block, block->data);
        if (ret != ENOERR) {
            return ret;
        }
        if (bc->prereadFun != NULL) {
            bc->prereadFun(bc, block);
        }
    }

    AddBlock(bc, block);

    *dblock = block;
    return ENOERR;
}

INT32 BcacheClearCache(OsBcache *bc)
{
    OsBcacheBlock *block = NULL;
    OsBcacheBlock *next = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(block, next, &bc->listHead, OsBcacheBlock, listNode) {
        DelBlock(bc, block);
    }
    return 0;
}

static INT32 BcacheInitCache(OsBcache *bc,
                             UINT8 *memStart,
                             UINT32 memSize,
                             UINT32 blockSize)
{
    UINT8 *blockMem = NULL;
    UINT8 *dataMem = NULL;
    OsBcacheBlock *block = NULL;
    UINT32 blockNum, i;

    LOS_ListInit(&bc->listHead);
    LOS_ListInit(&bc->numHead);
    bc->sumNum = 0;
    bc->nBlock = 0;

    if (!GetValLog2(blockSize)) {
        PRINT_ERR("GetValLog2(%u) return 0.\n", blockSize);
        return -EINVAL;
    }

    bc->rbRoot.rb_node = NULL;
    bc->memStart = memStart;
    bc->blockSize = blockSize;
    bc->blockSizeLog2 = GetValLog2(blockSize);
    bc->modifiedBlock = 0;

    /* init block memory pool */
    LOS_ListInit(&bc->freeListHead);

    blockNum = (memSize - DMA_ALLGN) / (sizeof(OsBcacheBlock) + bc->blockSize);
    blockMem = bc->memStart;
    dataMem = blockMem + (sizeof(OsBcacheBlock) * blockNum);
    dataMem += ALIGN_DISP((UINTPTR)dataMem);

#ifdef BCACHE_ANALYSE
    g_memSize = memSize;
    g_blockNum = blockNum;
    g_dataSize = bc->blockSize;
    g_memStart = dataMem;
#endif

    for (i = 0; i < blockNum; i++) {
        block = (OsBcacheBlock *)(VOID *)blockMem;
        block->data = dataMem;
        block->readBuff = (i < CONFIG_FS_FAT_READ_NUMS) ? TRUE : FALSE;

        if (i == CONFIG_FS_FAT_READ_NUMS) {
            bc->wStart = block;
        }

        LOS_ListTailInsert(&bc->freeListHead, &block->listNode);

        blockMem += sizeof(OsBcacheBlock);
        dataMem += bc->blockSize;
    }

    bc->wEnd = block;

    return ENOERR;
}

static INT32 DrvBread(struct Vnode *priv, UINT8 *buf, UINT32 len, UINT64 pos)
{
    struct block_operations *bops = (struct block_operations *)((struct drv_data *)priv->data)->ops;

    INT32 ret = bops->read(priv, buf, pos, len);
    if (ret != (INT32)len) {
        PRINT_ERR("%s failure\n", __FUNCTION__);
        return ret;
    }
    return ENOERR;
}

static INT32 DrvBwrite(struct Vnode *priv, const UINT8 *buf, UINT32 len, UINT64 pos)
{
    struct block_operations *bops = (struct block_operations *)((struct drv_data *)priv->data)->ops;
    INT32 ret = bops->write(priv, buf, pos, len);
    if (ret != (INT32)len) {
        PRINT_ERR("%s failure\n", __FUNCTION__);
        return ret;
    }
    return ENOERR;
}

INT32 BlockCacheDrvCreate(VOID *handle,
                          UINT8 *memStart,
                          UINT32 memSize,
                          UINT32 blockSize,
                          OsBcache *bc)
{
    INT32 ret;
    bc->priv = handle;
    bc->breadFun = DrvBread;
    bc->bwriteFun = DrvBwrite;

    ret = BcacheInitCache(bc, memStart, memSize, blockSize);
    if (ret != ENOERR) {
        return ret;
    }

    if (pthread_mutex_init(&bc->bcacheMutex, NULL) != ENOERR) {
        return VFS_ERROR;
    }
    bc->bcacheMutex.attr.type = PTHREAD_MUTEX_RECURSIVE;

    return ENOERR;
}

INT32 BlockCacheRead(OsBcache *bc, UINT8 *buf, UINT32 *len, UINT64 sector, BOOL useRead)
{
    OsBcacheBlock *block = NULL;
    UINT8 *tempBuf = buf;
    UINT32 size;
    UINT32 currentSize;
    INT32 ret = ENOERR;
    UINT64 pos;
    UINT64 num;
#ifdef BCACHE_ANALYSE
    PRINTK("bcache read:\n");
#endif

    if (bc == NULL || buf == NULL || len == NULL) {
        return -EPERM;
    }

    size = *len;
    pos = sector * bc->sectorSize;
    num = pos >> bc->blockSizeLog2;
    pos = pos & (bc->blockSize - 1);

    while (size > 0) {
        if ((size + pos) > bc->blockSize) {
            currentSize = bc->blockSize - (UINT32)pos;
        } else {
            currentSize = size;
        }

        (VOID)pthread_mutex_lock(&bc->bcacheMutex);

        /* useRead should be FALSE when reading large contiguous data */
        ret = BcacheGetBlock(bc, num, useRead, &block);
        if (ret != ENOERR) {
            (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
            break;
        }

        if ((block->readFlag == FALSE) && (block->modified == TRUE)) {
            ret = BcacheGetFlag(bc, block);
            if (ret != ENOERR) {
                (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
                return ret;
            }
        } else if ((block->readFlag == FALSE) && (block->modified == FALSE)) {
            ret = BlockRead(bc, block, block->data);
            if (ret != ENOERR) {
                (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
                return ret;
            }
        }

        if (LOS_CopyFromKernel((VOID *)tempBuf, size, (VOID *)(block->data + pos), currentSize) != EOK) {
            (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
            return VFS_ERROR;
        }

        (VOID)pthread_mutex_unlock(&bc->bcacheMutex);

        tempBuf += currentSize;
        size -= currentSize;
        pos = 0;
        num++;
    }
    *len -= size;
    return ret;
}

INT32 BlockCacheWrite(OsBcache *bc, const UINT8 *buf, UINT32 *len, UINT64 sector)
{
    OsBcacheBlock *block = NULL;
    const UINT8 *tempBuf = buf;
    UINT32 size = *len;
    INT32 ret = ENOERR;
    UINT32 currentSize;
    UINT64 pos;
    UINT64 num;
#ifdef BCACHE_ANALYSE
    PRINTK("bcache write:\n");
#endif

    pos = sector * bc->sectorSize;
    num = pos >> bc->blockSizeLog2;
    pos = pos & (bc->blockSize - 1);

    D(("bcache write len = %u pos = %llu bnum = %llu\n", *len, pos, num));

    while (size > 0) {
        if ((size + pos) > bc->blockSize) {
            currentSize = bc->blockSize - (UINT32)pos;
        } else {
            currentSize = size;
        }

        (VOID)pthread_mutex_lock(&bc->bcacheMutex);
        ret = BcacheGetBlock(bc, num, FALSE, &block);
        if (ret != ENOERR) {
            (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
            break;
        }

        if (LOS_CopyToKernel((VOID *)(block->data + pos), bc->blockSize - (UINT32)pos,
            (VOID *)tempBuf, currentSize) != EOK) {
            (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
            return VFS_ERROR;
        }
        if (block->modified == FALSE) {
            block->modified = TRUE;
            bc->modifiedBlock++;
        }
        if ((pos == 0) && (currentSize == bc->blockSize)) {
            memset_s(block->flag, sizeof(block->flag), 0xFF, sizeof(block->flag));
            block->allDirty = TRUE;
        } else {
            BcacheSetFlag(bc, block, (UINT32)pos, currentSize);
        }
        (VOID)pthread_mutex_unlock(&bc->bcacheMutex);

        tempBuf += currentSize;
        size -= currentSize;
        pos = 0;
        num++;
    }
    *len -= size;
    return ret;
}

INT32 BlockCacheSync(OsBcache *bc)
{
    return BcacheSync(bc);
}

INT32 OsSdSync(INT32 id)
{
#ifdef LOSCFG_FS_FAT_CACHE
    INT32 ret;
    los_disk *disk = get_disk(id);
    if ((disk == NULL) || (disk->disk_status == STAT_UNUSED)) {
        return VFS_ERROR;
    }
    if (pthread_mutex_lock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex lock fail!\n", __FUNCTION__, __LINE__);
        return VFS_ERROR;
    }
    if ((disk->disk_status == STAT_INUSED) && (disk->bcache != NULL)) {
        ret = BcacheSync(disk->bcache);
    } else {
        ret = VFS_ERROR;
    }
    if (pthread_mutex_unlock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex unlock fail!\n", __FUNCTION__, __LINE__);
        return VFS_ERROR;
    }
    return ret;
#else
    return VFS_ERROR;
#endif
}

INT32 LOS_BcacheSyncByName(const CHAR *name)
{
    INT32 diskID = los_get_diskid_byname(name);
    return OsSdSync(diskID);
}

INT32 BcacheGetDirtyRatio(INT32 id)
{
#ifdef LOSCFG_FS_FAT_CACHE
    INT32 ret;
    los_disk *disk = get_disk(id);
    if (disk == NULL) {
        return VFS_ERROR;
    }

    if (pthread_mutex_lock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex lock fail!\n", __FUNCTION__, __LINE__);
        return VFS_ERROR;
    }
    if ((disk->disk_status == STAT_INUSED) && (disk->bcache != NULL)) {
        ret = (INT32)((disk->bcache->modifiedBlock * PERCENTAGE) / GetFatBlockNums());
    } else {
        ret = VFS_ERROR;
    }
    if (pthread_mutex_unlock(&disk->disk_mutex) != ENOERR) {
        PRINT_ERR("%s %d, mutex unlock fail!\n", __FUNCTION__, __LINE__);
        return VFS_ERROR;
    }
    return ret;
#else
    return VFS_ERROR;
#endif
}

INT32 LOS_GetDirtyRatioByName(const CHAR *name)
{
    INT32 diskID = los_get_diskid_byname(name);
    return BcacheGetDirtyRatio(diskID);
}

#ifdef LOSCFG_FS_FAT_CACHE_SYNC_THREAD
static VOID BcacheSyncThread(UINT32 id)
{
    INT32 diskID = (INT32)id;
    INT32 dirtyRatio;
    while (1) {
        dirtyRatio = BcacheGetDirtyRatio(diskID);
        if (dirtyRatio > (INT32)g_dirtyRatio) {
            (VOID)OsSdSync(diskID);
        }
        msleep(g_syncInterval);
    }
}

VOID BcacheSyncThreadInit(OsBcache *bc, INT32 id)
{
    UINT32 ret;
    TSK_INIT_PARAM_S appTask;

    (VOID)memset_s(&appTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    appTask.pfnTaskEntry = (TSK_ENTRY_FUNC)BcacheSyncThread;
    appTask.uwStackSize = BCACHE_STATCK_SIZE;
    appTask.pcName = "bcache_sync_task";
    appTask.usTaskPrio = g_syncThreadPrio;
    appTask.auwArgs[0] = (UINTPTR)id;
    appTask.uwResved = LOS_TASK_STATUS_DETACHED;
    ret = LOS_TaskCreate(&bc->syncTaskId, &appTask);
    if (ret != ENOERR) {
        PRINT_ERR("Bcache sync task create failed in %s, %d\n", __FUNCTION__, __LINE__);
    }
}

VOID BcacheSyncThreadDeinit(const OsBcache *bc)
{
    if (bc != NULL) {
        if (LOS_TaskDelete(bc->syncTaskId) != ENOERR) {
            PRINT_ERR("Bcache sync task delete failed in %s, %d\n", __FUNCTION__, __LINE__);
        }
    }
}
#endif

OsBcache *BlockCacheInit(struct Vnode *devNode, UINT32 sectorSize, UINT32 sectorPerBlock,
                         UINT32 blockNum, UINT64 blockCount)
{
    OsBcache *bcache = NULL;
    struct Vnode *blkDriver = devNode;
    UINT8 *bcacheMem = NULL;
    UINT8 *rwBuffer = NULL;
    UINT32 blockSize, memSize;

    if ((blkDriver == NULL) || (sectorSize * sectorPerBlock * blockNum == 0) || (blockCount == 0)) {
        return NULL;
    }

    blockSize = sectorSize * sectorPerBlock;
    if ((((UINT64)(sizeof(OsBcacheBlock) + blockSize) * blockNum) + DMA_ALLGN) > UINT_MAX) {
        return NULL;
    }
    memSize = ((sizeof(OsBcacheBlock) + blockSize) * blockNum) + DMA_ALLGN;

    bcache = (OsBcache *)zalloc(sizeof(OsBcache));
    if (bcache == NULL) {
        PRINT_ERR("bcache_init : malloc %u Bytes failed!\n", sizeof(OsBcache));
        return NULL;
    }

    bcacheMem = (UINT8 *)zalloc(memSize);
    if (bcacheMem == NULL) {
        PRINT_ERR("bcache_init : malloc %u Bytes failed!\n", memSize);
        goto ERROR_OUT_WITH_BCACHE;
    }

    rwBuffer = (UINT8 *)memalign(DMA_ALLGN, blockSize);
    if (rwBuffer == NULL) {
        PRINT_ERR("bcache_init : malloc %u Bytes failed!\n", blockSize);
        goto ERROR_OUT_WITH_MEM;
    }

    bcache->rwBuffer = rwBuffer;
    bcache->sectorSize = sectorSize;
    bcache->sectorPerBlock = sectorPerBlock;
    bcache->blockCount = blockCount;

    if (BlockCacheDrvCreate(blkDriver, bcacheMem, memSize, blockSize, bcache) != ENOERR) {
        goto ERROR_OUT_WITH_BUFFER;
    }

    return bcache;

ERROR_OUT_WITH_BUFFER:
    free(rwBuffer);
ERROR_OUT_WITH_MEM:
    free(bcacheMem);
ERROR_OUT_WITH_BCACHE:
    free(bcache);
    return NULL;
}

VOID BlockCacheDeinit(OsBcache *bcache)
{
    if (bcache != NULL) {
        (VOID)pthread_mutex_destroy(&bcache->bcacheMutex);
        free(bcache->memStart);
        bcache->memStart = NULL;
        free(bcache->rwBuffer);
        bcache->rwBuffer = NULL;
        free(bcache);
    }
}

static VOID BcacheAsyncPrereadThread(VOID *arg)
{
    OsBcache *bc = (OsBcache *)arg;
    OsBcacheBlock *block = NULL;
    INT32 ret;
    UINT32 i;

    for (;;) {
        ret = (INT32)LOS_EventRead(&bc->bcacheEvent, PREREAD_EVENT_MASK,
                                   LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);
        if (ret != ASYNC_EVENT_BIT) {
            PRINT_ERR("The event read in %s, %d is error!!!\n", __FUNCTION__, __LINE__);
            continue;
        }

        for (i = 1; i <= PREREAD_BLOCK_NUM; i++) {
            if ((bc->curBlockNum + i) >= bc->blockCount) {
                break;
            }

            (VOID)pthread_mutex_lock(&bc->bcacheMutex);
            ret = BcacheGetBlock(bc, bc->curBlockNum + i, TRUE, &block);
            if (ret != ENOERR) {
                PRINT_ERR("read block %llu error : %d!\n", bc->curBlockNum, ret);
            }

            (VOID)pthread_mutex_unlock(&bc->bcacheMutex);
        }

        if (block != NULL) {
            block->pgHit = 1; /* preread complete */
        }
    }
}

VOID ResumeAsyncPreread(OsBcache *arg1, const OsBcacheBlock *arg2)
{
    UINT32 ret;
    OsBcache *bc = arg1;
    const OsBcacheBlock *block = arg2;

    if (OsCurrTaskGet()->taskID != bc->prereadTaskId) {
        bc->curBlockNum = block->num;
        ret = LOS_EventWrite(&bc->bcacheEvent, ASYNC_EVENT_BIT);
        if (ret != ENOERR) {
            PRINT_ERR("Write event failed in %s, %d\n", __FUNCTION__, __LINE__);
        }
    }
}

UINT32 BcacheAsyncPrereadInit(OsBcache *bc)
{
    UINT32 ret;
    TSK_INIT_PARAM_S appTask;

    ret = LOS_EventInit(&bc->bcacheEvent);
    if (ret != ENOERR) {
        PRINT_ERR("Async event init failed in %s, %d\n", __FUNCTION__, __LINE__);
        return ret;
    }

    (VOID)memset_s(&appTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    appTask.pfnTaskEntry = (TSK_ENTRY_FUNC)BcacheAsyncPrereadThread;
    appTask.uwStackSize = BCACHE_STATCK_SIZE;
    appTask.pcName = "bcache_async_task";
    appTask.usTaskPrio = BCACHE_PREREAD_PRIO;
    appTask.auwArgs[0] = (UINTPTR)bc;
    appTask.uwResved = LOS_TASK_STATUS_DETACHED;
    ret = LOS_TaskCreate(&bc->prereadTaskId, &appTask);
    if (ret != ENOERR) {
        PRINT_ERR("Bcache async task create failed in %s, %d\n", __FUNCTION__, __LINE__);
    }

    return ret;
}

UINT32 BcacheAsyncPrereadDeinit(OsBcache *bc)
{
    UINT32 ret = LOS_NOK;

    if (bc != NULL) {
        ret = LOS_TaskDelete(bc->prereadTaskId);
        if (ret != ENOERR) {
            PRINT_ERR("Bcache async task delete failed in %s, %d\n", __FUNCTION__, __LINE__);
        }

        ret = LOS_EventDestroy(&bc->bcacheEvent);
        if (ret != ENOERR) {
            PRINT_ERR("Async event destroy failed in %s, %d\n", __FUNCTION__, __LINE__);
            return ret;
        }
    }

    return ret;
}
