-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: SMC命令字接口
local class = require 'mc.class'
local bs = require "mc.bitstring"
local log = require 'mc.logging'
local smc = require 'protocol.smc'
local cmn = require 'common'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local upgrade_interface = require 'mcu.upgrade.upgrade_interface'

local smc_interface = class(upgrade_interface)

-- SMC初始化子件外部调用数据域格式
local smc_init_subcomp_upgrade_data = bs.new([[<<
    index:8,
    timeout:8,
    reserved:16,
    lib_size:32,
    bin_size:32
>>]])

-- SMC文件传输数据域格式
local smc_transfer_file_data = bs.new([[<<
    offset:32,
    data/string
>>]])

local SMC_SUBCOMP_UPGRADE_CNT_RESP_DATA_LEN = 0x03
local SMC_SUBCOMP_UPGRADE_DETAIL_RESP_DATA_LEN = 0x08
local SMC_SUBCOMP_UPGRADE_QUERY_RESP_DATA_LEN = 0x02
local SMC_SYSTEM_EVENT_RESP_DATA_LEN = 1
local SMC_VRD_DUMP_LEN_RESP_DATA_LEN = 2
local SMC_VRD_DUMP_RESP_DATA_LEN = 104
local SMC_SUBCOMP_UPGRADE_TIMEOUT = 5   -- 子件升级若不调用关闭接口，默认N分钟自动释放资源
local SMC_VRD_DATA_READ_OFFSET = 128 --获取VRD故障诊断信息param为偏移值，每次自增0x80（128字节） 
local MAX_RETRANS_TIMES<const> = 5 -- 每2k最大尝试次数5
local MAX_RETRY_TIMES<const> = 5 -- 读取smc命令字返回值防抖的最大重试次数5

function smc_interface:blkwrite(cmd, data)
    local ok, err = pcall(smc.chip_blkwrite, self.chip, cmd, data)
    if not ok then
        log:debug("SMC write to chip fail, err: %s", err)
        return false
    end
    return true
end

function smc_interface:blkread(cmd, len)
    return pcall(smc.chip_blkread, self.chip, cmd, len)
end

--- @function SMC查询可升级器件的信息
--- @param index any 子件索引号
--- @return any 子件升级信息
function smc_interface:get_subcomp_upgrade_info(index)
    -- func=0, command=0x61, ms=0, rw=1, param=index
    local cmd = 0x00018500 + index
    local rsp = self:smc_debounce(cmd, SMC_SUBCOMP_UPGRADE_DETAIL_RESP_DATA_LEN, MAX_RETRY_TIMES)
    if not rsp then
        log:debug("SMC read (get sub-component details) fail")
        return nil
    end

    local ret = {
        ['type'] = string.sub(rsp, 1, 1):byte(),
        ['no'] = string.sub(rsp, 2, 2):byte(),
        ['vendor'] = string.sub(rsp, 3, 3):byte(),
        ['sku'] = string.sub(rsp, 4, 4):byte(),
        ['major_version'] = string.sub(rsp, 5, 5):byte(),
        ['minor_version'] = string.sub(rsp, 6, 6):byte(),
        ['revision'] = string.sub(rsp, 7, 7):byte(),
        ['delay'] = string.sub(rsp, 8, 8):byte()
    }
    return ret
end

-- 对smc命令字做二值一致防抖
function smc_interface:smc_debounce(cmd, len, times)
    local ok, rsp1, rsp2
    
    for i = 1, times do
        ok, rsp1 = self:blkread(cmd, len)
        cmn.skynet.sleep(1)
        if not ok or not rsp1 then
            goto next
        end

        ok, rsp2 = self:blkread(cmd, len)
        cmn.skynet.sleep(1)
        if ok and rsp1 == rsp2 then
            break
        end
        ok = false
        ::next::
    end
    
    if not ok then
        return nil
    end

    return rsp1
end

local function is_vaild_detail(ret)
    if not ret then
        return false
    end
    if not ret['major_version'] or not ret['vendor'] or not ret['sku'] then
        return false
    end
    if ret['major_version'] == "" or ret['vendor'] == "" or ret['sku'] == "" then
        return false
    end
    if ret['major_version'] == 255 or ret['vendor'] == 255 or ret['sku'] == 255 then
        return false
    end
    return true
end

function smc_interface:get_subcomp_upgrade_detail(index)
    local ret = self:get_subcomp_upgrade_info(index)
    --index从1开始为vrdchip
    if index >= 1 then
        -- 防止读取失败的情况，需要加重试
        for _ = 1, 5 do
            if not is_vaild_detail(ret) then
                cmn.skynet.sleep(100)
                ret = self:get_subcomp_upgrade_info(index)
            else
                break
            end
        end
    end
    return ret
end

function smc_interface:get_vendor_id(sub_comp)
    if not sub_comp then
        return nil
    end
    local ret = self:get_subcomp_upgrade_detail(sub_comp.Index)
    if not ret then
        return nil
    end
    return ret['vendor']
end

function smc_interface:get_version(sub_comp)
    if not sub_comp then
        return nil
    end
    local ret = self:get_subcomp_upgrade_detail(sub_comp.Index)
    if not ret then
        return nil
    end
    return ret['major_version'], ret['minor_version'], ret['revision']
end

--- @function SMC初始化子件外部调用
--- @param index any 子件索引号
--- @return any 调用结果
function smc_interface:init_upgrade(sub_comp, lib_size, bin_size)
    if not sub_comp then
        return nil
    end
    local data = smc_init_subcomp_upgrade_data:pack({
        index = sub_comp.Index,
        timeout = SMC_SUBCOMP_UPGRADE_TIMEOUT,
        reserved = 0,
        lib_size = lib_size,
        bin_size = bin_size
    })

    -- func=0, command=0x62, ms=0, rw=0, param=0
    local cmd = 0x00018800
    local ok = self:blkwrite(cmd, data)
    -- 初始化与写入间隔，防止丢包，硬件建议等待2s
    cmn.skynet.sleep(200)
    if not ok then
        log:debug("SMC write (sub-component upgrade init) fail")
    end

    return ok
end

--- @function SMC查询子件升级能力
--- @return any 子件个数
function smc_interface:get_subcomp_upgrade_cnt()
    -- func=0, command=0x60, ms=0, rw=1, param=0
    local cmd = 0x00018100
    local rsp = self:smc_debounce(cmd, SMC_SUBCOMP_UPGRADE_CNT_RESP_DATA_LEN, MAX_RETRY_TIMES)
    if not rsp then
        log:debug("SMC read (get sub-component count) fail")
        return nil
    end

    local ret = {
        ['cnt'] = string.sub(rsp, 1, 1):byte(),
        ['lib_max_size'] = string.sub(rsp, 2, 2):byte() * 2048,
        ['bin_max_size'] = string.sub(rsp, 3, 3):byte() * 2048
    }

    return ret
end

--- @function SMC传输子件调用文件
--- @param file_type any 文件类型
--- @param offset any 文件数据偏移
--- @param file_data any 文件数据
--- @return any 传输结果（当前无法不支持返回新偏移长度）
function smc_interface:transfer_subcomp_upgrade_data(file_type, pack_data)
    -- func=0, command=0x63, ms=0, rw=0, param=0
    local cmd = 0x00018C00 + file_type

    cmn.skynet.fork(function(command, data)
        self:blkwrite(command, data)
    end, cmd, pack_data)

    return true
end

function smc_interface:query_next_frame_offset()
    -- func=0, command=0x69, ms=1, rw=0, param=0
    local command = 0x0001a500
    return self:blkread(command, 4)
end

function smc_interface:get_next_frame_offset_from_mcu(offset, retrans, params_list)
    for i = 1, 3 do -- 存在拿不到数据情况，最多尝试3次
        local ok, rsp = self:query_next_frame_offset()
        if ok and #rsp == 4 then
            local b4, b3, b2, b1 = rsp:byte(1, 4)
            -- 四字节长度偏移值，小端序列
            offset = (b1 << 24) + (b2 << 16) + (b3 << 8) + b4
            if retrans['last_2k_pos'] ~= (offset // params_list.subsection_size) then
                retrans['retry_times'] = 0
                retrans['last_2k_pos'] = (offset // params_list.subsection_size)
            else
                retrans['retry_times'] = retrans['retry_times'] + 1
            end
            log:error("query_next_frame retry_times:%s, new_offset:%s", retrans['retry_times'], offset)
            return offset, retrans
        else
            log:error("query_next_frame_offset failed ok:%s, retry:%s rsp len:%s", ok, i, #rsp)
        end
        cmn.skynet.sleep(1) --延时10ms
    end
    return offset, retrans
end

function smc_interface:get_next_frame_offset(offset, retrans, support_query, params_list)
    log:info("wait %s ms to transfer MCU file", params_list.delay * 10)
    -- 写入内存毫秒级，写入flash闪存4s
    local state = self:query_upgrade_status()
    cmn.skynet.sleep(params_list.delay)
    for _ = 1, 3 do
        if state ~= nil and state == MCU_ENUMS.MCU_UPGRADE_STATUS.INSYNC then
            if not support_query then
                return offset, retrans
            end
            log:notice("query_next_frame state:%s, offset:%s", state, offset)
            retrans['total_times'] = retrans['total_times'] + 1
            return self:get_next_frame_offset_from_mcu(offset, retrans, params_list)
        else
            state = self:query_upgrade_status()
            cmn.skynet.sleep(5) -- 状态获取失败50ms后重试至多3次
        end
    end
    return offset, retrans
end
--- @function SMC传输子件调用文件
--- @param file_type any 文件类型
--- @param data any 文件数据
--- @return any 新的文件数据偏移
function smc_interface:send_upgrade_file(sub_comp, data, params_list, process_callback, not_support_query)
    if not sub_comp then
        return nil
    end
    local length = #tostring(data)
    local offset = 0
    local fail_cnt = 0
    local written_len = 0
    local str, pack_data

    log:notice("wait %s ms to transfer MCU file each %s byte, len:%s", params_list.delay * 10,
        params_list.subsection_size, params_list.smc_data_len)

    local support_query = false
    if not not_support_query then
        support_query = self:query_next_frame_offset()
    end
    local retrans = {retry_times = 0, last_2k_pos = 0, total_times = 0}
    while offset < length do
        -- 最后一帧不足192字节取实际字节数
        if offset + params_list.smc_data_len >= length then
            written_len = length - offset
        else
            written_len = params_list.smc_data_len
        end

        str = string.sub(data, offset + 1, offset + written_len)
        pack_data = smc_transfer_file_data:pack({
            offset = offset,
            data = str
        })

        local ok = self:transfer_subcomp_upgrade_data(1, pack_data)
        if ok then
            offset = offset + written_len
        else
            fail_cnt = fail_cnt + 1
        end

        if fail_cnt > 10 then
            return false
        end

        -- 每发送subsection_size字节，延时delay * 10ms: 检测周期，每发送一定帧之后就进行实际发送成功内容确定
        if (offset // params_list.subsection_size) > (offset - written_len) // params_list.subsection_size or
            offset >= length then
            offset, retrans = self:get_next_frame_offset(offset, retrans, support_query, params_list)
        end
        if retrans['retry_times'] > MAX_RETRANS_TIMES then
            log:error("retry_times:%s > %s", retrans['retry_times'], MAX_RETRANS_TIMES)
            return false
        end
        if process_callback then
            process_callback(offset / length) 
        end
    end
    log:notice("send_upgrade_file retry total:%s", retrans['total_times'] )
    return true
end

--- @function SMC执行子件调用
function smc_interface:start_upgrade()
    -- func=0, command=0x64, ms=0, rw=0, param=0
    local cmd = 0x00019000
    
    local data = string.char(0x01)
    local ok = self:blkwrite(cmd, data)
    if not ok then
        log:debug("SMC write (start sub-component upgrade) fail")
    end

    return ok
end

--- @function SMC查询子件升级状态
--- @return any 升级结果
--- @return any 升级进度
function smc_interface:query_upgrade_status()
    -- func=0, command=0x65, ms=0, rw=1, param=0
    local cmd = 0x00019500
    local rsp = self:smc_debounce(cmd, SMC_SUBCOMP_UPGRADE_QUERY_RESP_DATA_LEN, MAX_RETRY_TIMES)
    if not rsp then
        log:debug("SMC write (query sub-component upgrade state) fail")
        return nil
    end

    local ret = {
        ['state'] = string.sub(rsp, 1, 1):byte(),
        ['progress'] = string.sub(rsp, 2, 2):byte()
    }
    return ret['state']
end

--- @function SMC关闭子件外部调用
--- @return any 升级结果
function smc_interface:take_upgrade_effect()
    -- func=0, command=0x66, ms=0, rw=0, param=0
    local cmd = 0x00019800
    
    local data = string.char(0x01)
    local ok = self:blkwrite(cmd, data)
    if not ok then
        log:debug("SMC write (sub-component upgrade deinit) fail")
    end

    return ok
end

--- @function 获取可升级子件个数
--- @return any 子件个数
--- @return any 库文件最大size
--- @return any 升级文件最大size
function smc_interface:get_upgrade_cnt()
    local timeout = 30  -- 重试30s
    local ret
    repeat
        ret = self:get_subcomp_upgrade_cnt()
        cmn.skynet.sleep(100)
        timeout = timeout - 1
    until ret and ret['cnt'] > 0 or timeout < 0

    if not ret then
        return nil, nil, nil
    end

    return ret['cnt'], ret['lib_max_size'], ret['bin_max_size']
end

--- @function SMC查询可升级器件的信息
--- @param index any 子件索引号
--- @return any 子件升级信息
function smc_interface:get_upgrade_detail(index)
    local timeout = 30 -- 重试30s
    local ret
    repeat
        ret = self:get_subcomp_upgrade_detail(index)
        cmn.skynet.sleep(100)
        timeout = timeout - 1
    until ret or timeout < 0
    return ret
end

--- @function 查询系统事件
--- @return any 系统事件结果
function smc_interface:get_system_event()
    local SYSTEM_EVENT_CMD <const> = 0x0C000900
    local ok, rsp = self:blkread(SYSTEM_EVENT_CMD, SMC_SYSTEM_EVENT_RESP_DATA_LEN)
    if not ok or not rsp then
        log:debug("SMC read (system event) fail, error:%s", rsp)
        return
    end

    local ret = rsp:byte()
    return ret
end

--- @function 查询Vrd故障寄存器
--- @return any Vrd故障信息
function smc_interface:get_vrd_dump_data()
    local ret = {}
    local VRD_DUMP_CMD <const> = 0x0C03D500 -- 该命令字最后一位是偏移N
    local VRD_LEN_DUMP_CMD <const> = 0x0C03D900 --获取vrd故障诊断信息长度
    local ok, rsp = self:blkread(VRD_LEN_DUMP_CMD, SMC_VRD_DUMP_LEN_RESP_DATA_LEN)
    local smc_dump_len = SMC_VRD_DUMP_RESP_DATA_LEN --读取长度失败则使用原始长度
    if not ok or not rsp then
        log:error('SMC read (vrd dump len) fail, error:%s', rsp)
    else
        smc_dump_len = string.unpack('H', rsp)
    end
    local n = smc_dump_len // SMC_VRD_DATA_READ_OFFSET
    local read_len = SMC_VRD_DATA_READ_OFFSET
    for i = 0, n do
        -- 如果是最后一次，即剩余长度<=128，则一次读取剩余长度的诊断信息
        -- 如果不是最后一次读取，则每次读取128
        read_len = (i == n and (smc_dump_len % SMC_VRD_DATA_READ_OFFSET) or SMC_VRD_DATA_READ_OFFSET)
        if read_len == 0 then
            break
        end
        ok, rsp = self:blkread(VRD_DUMP_CMD + i, read_len)
        if not ok or not rsp then
            log:error("SMC read (vrd dump) fail, error:%s", rsp)
            return ret
        end
        for j = 1, #rsp do
            table.insert(ret, string.sub(rsp, j, j):byte())
        end
    end
    return ret
end
 
function smc_interface:get_vrd_dump()
    local timeout = 10
    local ret
    repeat
        ret = self:get_vrd_dump_data()
        cmn.skynet.sleep(50)
        timeout = timeout - 1
    until next(ret) or timeout < 0
    if not next(ret) then
        log:error('[VrdDump]get vrd dump failed')
    else
        log:notice('[VrdDump]get vrd dump cnt %s', #ret)
    end
    return ret
end

--- @function 查询生效模式
--- @return any 生效模式
--- 查询失败时默认为直接生效，升级结束后直接执行生效动作
function smc_interface:query_active_mode()
    local cmd = 0x24009500
    local rsp = self:smc_debounce(cmd, 0x01, MAX_RETRY_TIMES)
    if not rsp then
        log:debug("SMC read (query_active_mode) failed, error:%s", rsp)
        return MCU_ENUMS.ACTIVE_IMMEDIATELY
    end

    local ret = rsp:byte()
    return ret
end

--- @function 执行生效动作
--- @return any 生效模式
--- MCU生效流程归一，组件可能未实现此命令字，默认无生效动作
function smc_interface:exec_valid_action()
    local MCU_ACTIVE_MODE <const> = 0x24009600
    local data = string.char(0x00)
    local ok = self:blkwrite(MCU_ACTIVE_MODE, data)
    if not ok then
        log:debug("SMC write (active_mcu) failed!")
    end
end

return smc_interface