-- 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
local MAX_PARAM_VALUE<const> = 0xFF -- smc命令字param最大只支持255
local SMC_MCU_LOG_TYPE_LIST<const> = 4

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_interface:batch_write(data)
    local ok, err = pcall(smc.chip_batch_write, self.chip, data)
    if not ok then
        log:error("SMC batch_write to chip fail, err: %s", err)
        return false
    end
    return true
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_interface:pack_upgrade_frame(data, offset, length, smc_data_len)
    -- 最后一帧不足192字节取实际字节数
    local written_len
    if offset + smc_data_len >= length then
        written_len = length - offset
    else
        written_len = smc_data_len
    end

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

function smc_interface:send_acu_upgrade_file(sub_comp, data, params_list, process_callback)
    if not sub_comp then
        return nil
    end
    local length = #tostring(data)
    local offset = 0
    local written_len = 0
    local pack_data
    local write_data_batch = {}
    local cmd = 0x00018C00 + 1 -- 命令字和transfer_subcomp_upgrade_data使用场景一致
    local first_frame_sent = false

    log:notice("transfer MCU file each %s byte, len:%s", params_list.subsection_size, params_list.smc_data_len)
    while offset < length do
        pack_data, written_len = self:pack_upgrade_frame(data, offset, length, params_list.smc_data_len)
        write_data_batch[#write_data_batch + 1] = {cmd, pack_data}
        offset = offset + written_len

        -- 每发送subsection_size字节，延时200ms
        if (offset // params_list.subsection_size) > (offset - written_len) // params_list.subsection_size or
            offset >= length then
            cmn.skynet.fork(function(write_data)
                self:batch_write(write_data)
            end, write_data_batch)
            cmn.skynet.sleep(20)
            write_data_batch = {}
            -- 第一次发送检查升级状态
            if not first_frame_sent then
                first_frame_sent = true
                local status = self:query_upgrade_status()
                if status ~= MCU_ENUMS.SMC_CALL_STATUS_CODE.BUSY then
                    log:error("First frame sent but MCU not in BUSY status, status: %s", status)
                    return false
                end
            end
        end
        if process_callback then
            process_callback(offset / length)
        end
    end
    return true
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)
    if not sub_comp then
        return nil
    end
    local length = #tostring(data)
    local offset = 0
    local fail_cnt = 0
    local written_len = 0
    local 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 = self:query_next_frame_offset()
    local retrans = {retry_times = 0, last_2k_pos = 0, total_times = 0}
    while offset < length do
        pack_data, written_len = self:pack_upgrade_frame(data, offset, length, params_list.smc_data_len)

        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

local VRD_DATA_LEN = 13
--- @function 查询Vrd故障寄存器
--- @return any Vrd故障信息
function smc_interface:get_vrd_dump_specification()
    local VRD_DUMP_SPECIFICATION_CMD <const> = 0x0C03D900
    local ok, rsp = self:blkread(VRD_DUMP_SPECIFICATION_CMD, 3)
    if not ok or not rsp then
        log:error("SMC read (vrd dump specification) fail, error:%s", rsp)
        return

    end
    local b1, b2 = string.byte(rsp, 1, 2)
    local total_len = (b2 << 8) | b1
    local register_len = string.byte(rsp, 3)
    -- MCU不支持register_len长度获取失败使用默认值
    if register_len == 0 then
        register_len = VRD_DATA_LEN
    end
    local register_num = total_len // register_len
    log:notice("total_len:%s register_num:%s register_len:%s", total_len, register_num, register_len)
    return total_len, register_len
end

function smc_interface:get_vrd_dump_data()
    local total_dump_info = {}
    local total_len, total_count = self:get_vrd_dump_specification()
    if not total_len or not total_count then
        -- mcu版本不支持时，使用默认的总长度
        total_len = SMC_VRD_DUMP_RESP_DATA_LEN
        total_count = VRD_DATA_LEN
    end
    local VRD_DUMP_CMD <const> = 0x0C03D500
    local n = total_len // SMC_VRD_DATA_READ_OFFSET
    -- VRD_DUMP_CMD后8位是param，超过0xFF会导致命令字越界
    if n > MAX_PARAM_VALUE then
        log:error('[vrd_dump] vrd dump length too large, total_len: %s', total_len)
        return total_dump_info, total_count
    end
    local ok, rsp, read_len
    for i = 0, n do
        -- 如果是最后一次，即剩余长度<=128, 则一次读取剩余长度的诊断信息
        -- 如果不是最后一次读取，则每次读取128
        read_len = (i == n and (total_len % SMC_VRD_DATA_READ_OFFSET) or SMC_VRD_DATA_READ_OFFSET)
        log:notice('[vrd_dump]The %s times get vrd data, read_len: %s', i, read_len)
        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 total_dump_info, total_count
        end
        for j = 1, #rsp do
            table.insert(total_dump_info, string.sub(rsp, j, j):byte())
        end
    end

    return total_dump_info, total_count
end
 
function smc_interface:get_vrd_dump()
    local timeout = 10
    local total_dump_info
    local total_count
    repeat
        total_dump_info, total_count = self:get_vrd_dump_data()
        cmn.skynet.sleep(50)
        timeout = timeout - 1
    until next(total_dump_info) ~= nil and #total_dump_info > 0 or timeout < 0
    if not next(total_dump_info) or #total_dump_info == 0 then
        log:error('[VrdDump]get vrd dump failed')
    else
        log:notice('[VrdDump]get vrd dump cnt %s', #total_dump_info)
    end
    return total_dump_info, total_count
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

--- @function 复位mcu
function smc_interface:start_reset_mcu()
    --func=10, command=0xA5, ms=0, rw=0, param=0
    local cmd = 0x40029400

    local data = string.char(0x01)
    local ok = self:blkwrite(cmd, data)
    if not ok then
        log:debug("SMC write (reset_mcu) fail")
    end

    return ok
end

--- @funciton h获取MCU是否支持日志收集和可收集的日志类型
--- @return any 是否支持日志收集
--- @return any 可收集的日志类型
--- func = 00h, command=0x70, ms=0, rw=1, param=0
function smc_interface:get_type_list()
    local cmd = 0x1C100
    local type_list = {}
    local data = 0
    local ok, rsp = self:blkread(cmd, 4)
    if not ok or not rsp then
        log:debug('smc read (get type list) failed, rsp = %s', rsp)
        return nil
    end
    for i = 1, SMC_MCU_LOG_TYPE_LIST do
        data = string.sub(rsp, i, i):byte()
        table.insert(type_list, data)
    end
    local is_supported = type_list[1] & 1
    return is_supported, type_list
end

--- @funciton h获取MCU特性类型日志的基本信息
--- @param type any 日志类型
--- @return u8 单个type类型的总帧数
--- @return u8 单帧长度
--- @return u8 日志格式（1：reg, 2:txt)
--- func = 00h, command=0x71, ms=0, rw=1, param=0
function smc_interface:get_mcu_type_info(type)
    local cmd = 0x1C500 + type
    local ok, rsp = self:blkread(cmd, 3)
    if not ok  or not rsp then
        log:error('smc read (get mcu type info) failed, rsp = %s', rsp)
        return nil
    end

    local ret = {
        ['num_total_frames'] = string.sub(rsp, 1, 1):byte(),
        ['bytes_per_frame'] = string.sub(rsp, 2, 2):byte(),
        ['format'] = MCU_ENUMS.MCU_LOG_FORMAT[string.sub(rsp, 3, 3):byte()]
    }
    return ret
end

--- @funciton 获取单帧数据
--- @param index any当前帧的序号
--- @param len any 单帧长度
--- @param format any 日志格式
--- @return string 单帧数据
--- func = 00h, command=0x72, ms=0, rw=1, param=0
function smc_interface:get_single_frame(type_info)
    local retry_times = 3
    local cmd = 0x1C900
    local single_data = ''
    local log_data = ''
    local hex_data = ''
    local ok 
    for offset = 1, type_info['num_total_frames'], 1 do
        for i = 1, retry_times, 1 do
            ok, single_data = self:blkread(cmd + offset, type_info['bytes_per_frame'])
            if ok and #single_data ~= 0 then
                break
            end
            log:error('smc read (get mcu single frame) failed, offset = %s, index = %s, rsp = %s', offset, i , single_data)
        end
        log_data = log_data .. single_data
    end

    if type_info['format'] == 'reg' then
        for i = 1, #log_data do
            hex_data = hex_data .. string.format('0x%02X ', string.byte(log_data, i))
        end
        return hex_data
    end
    return log_data
end

--@function 清除特定类型日志
--@param type any 日志类型
function smc_interface:clear_mcu_log()
    local cmd = 0x1CC00
    local data = string.char(0x00)
    local ok = self:blkwrite(cmd, data)
    if not ok then
        log:error('smc write (clear mcu log) failed')
    end
end

return smc_interface