/*
 * \file        optimus_buffer_manager.c
 * \brief       buffer manager for download data: A thin layer between receiving partition data and writing flash
 *
 * \version     1.0.0
 * \date        2013/5/2
 * \author      Sam.Wu <yihui.wu@Amlogic.com>
 *
 * Copyright (c) 2013 Amlogic Inc.. All Rights Reserved.
 *
 */
#include "../v2_burning_i.h"

#define OPTIMUS_SLOT_STA_FREE               (0)//buffer slot not used yet
#define OPTIMUS_SLOT_STA_USED               (0xee)//buffer slot current used for download
#define OPTIMUS_SLOT_STA_LEFT               (0xdd)//buffer slot not disposed over 

#define PKT_TRANSFER_STA_EMPTY              0
#define PKT_TRANSFER_STA_WORKING            1
#define PKT_TRANSFER_STA_END                2

typedef struct bufManager{
    const u8*       transferBuf;//transfer buffer address
    const u32       transferBufSz;//transfer buffer size to 

    const u32       transferUnitSz;//64k
    u32             writeBackUnitSz;//for NAND is transferSz, for sparse is transferUnitSz

    u64             tplcmdTotalSz;//total size of a file-system packet

    u32             totalSlotNum;//total slot number that already tranfferred
    u32             mediaAlignSz;//nand write align size, 16K/32k

    u32             nextWriteBackSlot;//when reach n* (writeBackUnitSz/transferUnitSz), then write back the recevied data to media
    u32             leftDataSz;//left data size, Assert that 'leftDataInBackBuf + leftDataSz == transferBuf'

    s16             isUpload;
    s16             pktTransferSta;
    u32             destMediaType;

    u32             partBaseOffset;//TODO: change it to memory address when dest media type is memory
    u32             itemOffsetNotAlignClusterSz_f;//For sdcard burning, item offset of aml_upgrade_package.img is not aligned to bytespercluster of FAT fs(_f means not changed inited)

}BufManager;

//TODO: if want to speed-up such as soft PING-PONG buffer, use multiple BufManager
static BufManager _bufManager = 
{
//constant members
    .transferBuf        = (const u8*)OPTIMUS_DOWNLOAD_TRANSFER_BUF_ADDR,
    .transferBufSz      = OPTIMUS_DOWNLOAD_TRANSFER_BUF_TOTALSZ,
    .transferUnitSz     = OPTIMUS_DOWNLOAD_SLOT_SZ,

//different for each command, note a command corresponding to a download file
//must create/destroy for a command
    .writeBackUnitSz    = OPTIMUS_DOWNLOAD_SLOT_SZ,

    .totalSlotNum      = 0,//not slot data recevied yet!

    .leftDataSz         = 0,
    .tplcmdTotalSz      = 0,
    .nextWriteBackSlot  = 0,//always 0 when upload??

    .isUpload           = 0,
    .pktTransferSta     = PKT_TRANSFER_STA_EMPTY,

    .itemOffsetNotAlignClusterSz_f  = 0,
};

int optimus_buf_manager_init(const unsigned mediaAlignSz)
{
    if(OPTIMUS_DOWNLOAD_TRANSFER_BUF_ADDR != (u32)_bufManager.transferBuf){
        DWN_ERR("Fatal fail in init-express, init here instead!\n");
        return OPT_DOWN_FAIL;
    }
    _bufManager.mediaAlignSz = mediaAlignSz;

    DWN_DBG("transfer=0x%p, transferBufSz=0x%x, transferUnitSz=0x%x, writeBackUnitSz=0x%x, totalSlotNum=%d\n", _bufManager.transferBuf,        
            _bufManager.transferBufSz,  _bufManager.transferUnitSz,     _bufManager.writeBackUnitSz,        _bufManager.totalSlotNum);

    return OPT_DOWN_OK;
}

int optimus_buf_manager_exit(void)
{
    return 0;
}

int optimus_buf_manager_tplcmd_init(const char* mediaType,  const char* partName,   const u64 partBaseOffset, 
                            const char* imgType, const u64 pktTotalSz, const int isUpload, 
                            const unsigned itemSizeNotAligned /* if item offset 3 and bytepercluste 4k, then it's 4k -3 */)
{
    u32 writeBackUnitSz = OPTIMUS_VFAT_IMG_WRITE_BACK_SZ;
    const u64 pktSz4BufManager = pktTotalSz - itemSizeNotAligned;

    if(!strcmp("sparse", imgType) 
            || itemSizeNotAligned/* use max memory if item 'itemOffset % bytespercluster != 0'*/)
    {
        writeBackUnitSz = OPTIMUS_SIMG_WRITE_BACK_SZ;
    }

    if(!strcmp("bootloader", partName))
    {
        if(pktSz4BufManager > _bufManager.transferBufSz) {
            DWN_ERR("packet size 0x%x too large, max is 0x%x\n", (u32)pktSz4BufManager, _bufManager.transferBufSz);
            return OPT_DOWN_FAIL;
        }
        writeBackUnitSz = OPTIMUS_BOOTLOADER_MAX_SZ;
    }

    _bufManager.destMediaType   = OPTIMUS_MEDIA_TYPE_STORE;
    if(!strcmp("mem", mediaType))
    {
        /*writeBackUnitSz             = OPTIMUS_MEMORY_WRITE_BACK_SZ;*/
            writeBackUnitSz             = pktSz4BufManager + _bufManager.transferUnitSz - 1;
            writeBackUnitSz             >>= OPTIMUS_DOWNLOAD_SLOT_SZ_SHIFT_BITS;
            writeBackUnitSz             <<= OPTIMUS_DOWNLOAD_SLOT_SZ_SHIFT_BITS;

        _bufManager.destMediaType   = OPTIMUS_MEDIA_TYPE_MEM;

        if(partBaseOffset>>32){
            DWN_ERR("partBaseOffset 0x%llx more than 4G!!\n", partBaseOffset);
            return OPT_DOWN_FAIL;
        }
        _bufManager.partBaseOffset = (u32)partBaseOffset;
    }

    if(_bufManager.transferBufSz < writeBackUnitSz && strcmp("mem", mediaType)){
        DWN_ERR("write back size 0x%x > max size 0x%x\n", writeBackUnitSz, _bufManager.transferBufSz);
        return OPT_DOWN_FAIL;
    }
    if(_bufManager.transferUnitSz > writeBackUnitSz){
        DWN_ERR("write back size %d < align size %d\n", writeBackUnitSz, _bufManager.mediaAlignSz);
        return OPT_DOWN_FAIL;
    }
    DWN_DBG("writeBackUnitSz = 0x%x, pktSz4BufManager = %lld\n", writeBackUnitSz, pktSz4BufManager);

    _bufManager.writeBackUnitSz     = writeBackUnitSz;
    _bufManager.totalSlotNum        = 0;
    _bufManager.isUpload            = isUpload;
    _bufManager.pktTransferSta      = PKT_TRANSFER_STA_EMPTY;

    if(_bufManager.isUpload)
    {
        _bufManager.nextWriteBackSlot = 0;//always 0 if upload
    }
    else//has write back if download
    {
        if(pktSz4BufManager < writeBackUnitSz)
        {
            _bufManager.nextWriteBackSlot  = ((u32)pktSz4BufManager + _bufManager.transferUnitSz - 1)/_bufManager.transferUnitSz;//first slot index to write back to media
        }
        else
        {
            _bufManager.nextWriteBackSlot  = writeBackUnitSz/_bufManager.transferUnitSz;//first slot index to write back to media
        }

    }

    _bufManager.itemOffsetNotAlignClusterSz_f = itemSizeNotAligned;
    _bufManager.leftDataSz                  = itemSizeNotAligned;//data size in the buffer that not write back to media yet in previous transfer
    _bufManager.tplcmdTotalSz               = pktSz4BufManager;
    
    optimus_progress_init((u32)(_bufManager.tplcmdTotalSz>>32), (u32)_bufManager.tplcmdTotalSz, 0, 100);
    DWN_MSG("totalSlotNum = %d, nextWriteBackSlot %d\n", _bufManager.totalSlotNum, _bufManager.nextWriteBackSlot);

    return OPT_DOWN_OK;
}

int optimus_buf_manager_get_buf_for_bulk_transfer(char** pBuf, const unsigned wantSz, const unsigned sequenceNo, char* errInfo)
{
    const unsigned totalSlotNum      = _bufManager.totalSlotNum;
    const u64 totalTransferSz        = ((u64)totalSlotNum) * _bufManager.transferUnitSz;//data size already transferred
    const u64 leftPktSz              = (totalTransferSz > _bufManager.tplcmdTotalSz) ? 0 :(_bufManager.tplcmdTotalSz - totalTransferSz);
    const int isLastTransfer         = (leftPktSz == wantSz);//totalTransferSz + wantSz >= _bufManager.tplcmdTotalSz;
    const u32 bufSzNotDisposed       = ((u32)totalTransferSz)% _bufManager.writeBackUnitSz;//buffer data not disposed, bufSz is always writeBackUnitSz
    const u32 BufBase = (OPTIMUS_MEDIA_TYPE_MEM != _bufManager.destMediaType)  ? (unsigned)_bufManager.transferBuf : _bufManager.partBaseOffset ;
    
    if(wantSz < _bufManager.transferUnitSz && !isLastTransfer){
        DWN_ERR("only last transfer can less 64K, this index %d at size 0x%u illegle\n", totalSlotNum + 1, wantSz);
        return OPT_DOWN_FAIL;
    }

    //TODO: totalSlotNum + 1 == sequenceNo
    if(totalSlotNum + 1 != sequenceNo){//ASSERT it ??

    }

    *pBuf  = (char*)bufSzNotDisposed + BufBase;
    DWN_DBG("bufSzNotDisposed 0x%x, _bufManager.transferBuf 0x%p, _bufManager.partBaseOffset 0x%x, *pBuf 0x%p\n", 
            bufSzNotDisposed, _bufManager.transferBuf, _bufManager.partBaseOffset, *pBuf);

    _bufManager.pktTransferSta      = PKT_TRANSFER_STA_WORKING;

    //prepare data for upload
    if(!bufSzNotDisposed && _bufManager.isUpload)
    {
        u32 wantSz = (leftPktSz > _bufManager.writeBackUnitSz) ? _bufManager.writeBackUnitSz : ((u32)leftPktSz);
        DWN_DBG("want size 0x%x\n", wantSz);

        u32 readSz = optimus_dump_storage_data((u8*)BufBase, wantSz, errInfo);
        if(readSz != wantSz){
            DWN_ERR("Want read %u, but %u\n", wantSz, readSz);
            return OPT_DOWN_FAIL;
        }
    }

    return OPT_DOWN_OK;
}

int optimus_buf_manager_report_transfer_complete(const u32 transferSz, char* errInfo)
{
    const unsigned totalSlotNum      = _bufManager.totalSlotNum;
    const u64 totalTransferSz        = ((u64)totalSlotNum) * _bufManager.transferUnitSz + transferSz;
    const u64 leftPktSz              = (totalTransferSz > _bufManager.tplcmdTotalSz) ? 0 :(_bufManager.tplcmdTotalSz - totalTransferSz);
    const u32 thisWriteBackSz        = (_bufManager.transferUnitSz == transferSz) ? _bufManager.writeBackUnitSz : ((u32)totalTransferSz % _bufManager.writeBackUnitSz);
    const u32 BufBase = (OPTIMUS_MEDIA_TYPE_MEM != _bufManager.destMediaType)  ? (unsigned)_bufManager.transferBuf : _bufManager.partBaseOffset ;
    
    DWN_DBG("transferSz=0x%x\n", transferSz);
    //state fileds to update
    _bufManager.totalSlotNum += 1;
    if(_bufManager.totalSlotNum == _bufManager.nextWriteBackSlot)
    {
        u32   burnSz   = 0;
        u32   leftSz   = _bufManager.leftDataSz;//data size not write to media in previous write back, > 0 only when not normal packet
        const u32 size = leftSz + thisWriteBackSz;
        const u8* data = (u8*)BufBase -leftSz;
        const unsigned reserveNotAlignSz = leftPktSz ? _bufManager.itemOffsetNotAlignClusterSz_f : 0;//reserve 

        //call cb function to write to media
        DWN_DBG("size 0x%x, reserveNotAlignSz 0x%x\n", size, reserveNotAlignSz);
        burnSz = optimus_download_img_data(data, size - reserveNotAlignSz, errInfo);
        if(burnSz <= leftSz || !burnSz){
            DWN_ERR("this burn size %d <= last left size %d, data 0x%p\n", burnSz, leftSz, data);
            return OPT_DOWN_FAIL;
        }
        if(size - reserveNotAlignSz < burnSz){
            DWN_ERR("Exception:siz 0x%x < burnSz 0x%x\n", size - reserveNotAlignSz, burnSz);
            return OPT_DOWN_FAIL;
        }

        leftSz = size - burnSz;
        if(leftSz)
        {
            const u8* src = data + burnSz;
            u8* dest = (u8*)BufBase - leftSz;

            if(totalTransferSz >= _bufManager.tplcmdTotalSz) {
                DWN_ERR("Exception:packet end but data left 0x%x, totalTransferSz 0x%llx, cmd sz 0x%llx!\n", 
                        leftSz, totalTransferSz, _bufManager.tplcmdTotalSz);
                return OPT_DOWN_FAIL;
            }

            if(leftSz > OPTIMUS_SPARSE_IMG_LEFT_DATA_MAX_SZ){
                DWN_ERR("Exception, left data sz 0x%x > back buf sz 0x%x!\n", leftSz, OPTIMUS_SPARSE_IMG_LEFT_DATA_MAX_SZ);
                return OPT_DOWN_FAIL;
            }
            if(leftSz & 0x03){
                DWN_ERR("Exception, copy size not align to 4! May will copy fail!\n");
                return OPT_DOWN_FAIL;
            }

            DWN_DBG("MV:left size 0x%08x, src %p, dest %p\n", leftSz, src, dest);
            memcpy(dest, src, leftSz);
        }

        //update _bufManager.leftDataSz and _bufManager.nextWriteBackSlot
        _bufManager.leftDataSz = leftSz; 
        if(leftPktSz >= _bufManager.writeBackUnitSz)
        {
            _bufManager.nextWriteBackSlot += _bufManager.writeBackUnitSz/_bufManager.transferUnitSz;
        }
        else
        {
            _bufManager.nextWriteBackSlot += ((u32)leftPktSz + _bufManager.transferUnitSz - 1)/_bufManager.transferUnitSz;
        }
    }

    optimus_update_progress(transferSz);//report burning steps
    return OPT_DOWN_OK;
}

int is_largest_data_transferring(void)
{
    const unsigned totalSlotNum      = _bufManager.totalSlotNum;
    const u64 totalTransferSz        = ((u64)totalSlotNum) * _bufManager.transferUnitSz;//data size already transferred

    return (PKT_TRANSFER_STA_WORKING == _bufManager.pktTransferSta) 
        && (_bufManager.tplcmdTotalSz > totalTransferSz);//as last packet may less than 64k, var totalSlotNum may > _bufManager.tplcmdTotalSz
}

int set_largest_data_transfer_sta_end(void)
{
    _bufManager.pktTransferSta = PKT_TRANSFER_STA_END; 
    return 0;
}

//command data format: [0-3]reserver, [4-7]dataLen, [8-11]sequence number, [12-15]check sum
int optimus_buf_manager_get_command_data_for_upload_transfer(u8* cmdDataBuf, const unsigned bufLen)
{
    const unsigned totalSlotNum      = _bufManager.totalSlotNum;
    const u64 totalTransferSz        = ((u64)totalSlotNum) * _bufManager.transferUnitSz;
    const u64 leftPktSz              = (totalTransferSz > _bufManager.tplcmdTotalSz) ? 0 :(_bufManager.tplcmdTotalSz - totalTransferSz);
    const unsigned thisTransDataLen = (leftPktSz > _bufManager.transferUnitSz) ? _bufManager.transferUnitSz : ((u32)leftPktSz);

    DWN_DBG("thisTransDataLen 0x%x, left 0x%x, total 0x%x\n", thisTransDataLen, (u32)leftPktSz, (u32)totalTransferSz); 
    DWN_DBG("totalSlotNum %d, totalTransferSz 0x%x\n", totalSlotNum, (u32)totalTransferSz);
    memset(cmdDataBuf, bufLen, 0);
    *(unsigned*)(cmdDataBuf + 0) = 0xefe8;
    *(unsigned*)(cmdDataBuf + 4) = thisTransDataLen;//Fill transfer data length of this bulk transfer

    _bufManager.pktTransferSta      = PKT_TRANSFER_STA_WORKING;
    return 0;
}


