-- 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: 定义CPLD升级的生效过程
local skynet = require 'skynet'
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local vos = require 'utils.vos'
local log = require 'mc.logging'
local bs = require 'mc.bitstring'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local drivers = require 'unit_manager.class.logic_fw.upgrade.drivers_api'
local ctx = require 'mc.context'
local fructl = require 'mcu.upgrade.fructl_handler'
local FRUCTL_LOCK_TIME<const> = 300
local factory = require 'factory'

local valid = {}

function valid:init_valid_file(logic_fw, chip_index, source_file, file_type, hot_upgrade)
    if string.find(logic_fw.csr.Name, 'FPGA') then
        return
    end
    local file_dir = defs.UPGRADE_CPLD_VALID_FILE_DIR
    if hot_upgrade then
        file_dir = defs.HOT_UPGRADE_CPLD_VALID_FILE_DIR
    end
    if not vos.get_file_accessible(file_dir) then
        utils.mkdir_with_parents(file_dir, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    end
    if not logic_fw.system_id or not logic_fw.uid then
        log:error('[cpld]unsupport firmware: system_id or uid not exist')
        return
    end
    local target_path = string.format('%s/valid_%s_%x_%s.%s',
                                      file_dir,
                                      logic_fw.uid, chip_index, logic_fw.system_id, file_type)
    utils.copy_file(source_file, target_path)
end

local function set_cpld_validating_flag(obj_fw, flag)
    local unit_manager = factory.get_obj("unit_manager")
    local board_obj = unit_manager:get_board_obj_by_position(obj_fw.position)
    if board_obj then
        board_obj.cpld_validating = flag
    end
end

-- 加载cpld,将升级文件拷贝到cpld flash中
local function valid_cpld(obj_fw, valid_file, file_type)
    local cpld_upgrade_rst_mode = 0
    local file_name_len = #valid_file
    local MAX_CPLD_LEN = 0x300000 -- 驱动限制为3M
    local file_len = vos.get_file_length(valid_file)
    if file_len > MAX_CPLD_LEN or file_len <= 0 then
        log:error('[cpld]file len:0x%x(Max:0x%x) invalid!', file_len, MAX_CPLD_LEN)
        return defs.RET.ERR
    end
    log:notice('[cpld]valid file[0x%x]:%s', file_len, valid_file)
    local upg_info = {
        mode = cpld_upgrade_rst_mode,
        product_id = 0,
        board_id = 0,
        pcb = 0x43, -- V2中用字符C赋值，这里直接用ascii码0x43表示
        component_id = 0, -- V3采用CSR配置每一种JTAG链路模式，不用再进行链路选择，cid默认填0
        file_type = file_type,
        filename_len = file_name_len,
        filename = valid_file
    }
    -- 按照V2 CPLD_UPG_FILE_S结构体小端对齐封装
    -- V2的CPLD_UPG_FILE_S结构体设计未考虑字节序对齐，mode需要修改为4字节
    local CPLD_UPG_FILE_DESC = string.format(
                                   '<<mode:4/unit:8, product_id:4/little-unit:8, ' ..
                                       'board_id:4/little-unit:8, ' ..
                                       'pcb:4/little-unit:8, component_id:4/little-unit:8, ' ..
                                       'file_type:3/string, filename_len:4/little-unit:8, filename:%d/binary>>',
                                       file_name_len)
    local info = bs.new(CPLD_UPG_FILE_DESC):pack(upg_info)
    local ok, error = pcall(obj_fw.update_chip.Write, obj_fw.update_chip,
                            require'mc.context'.new(), 0, info)
    if not ok then
        log:error('[cpld]block_write fail:%s', error)
        return defs.RET.ERR
    end
    log:notice('[cpld]%s update success!!!!', valid_file)
    skynet.sleep(50) -- 等待 500 ms
    return defs.RET.OK
end

function valid:get_valid_file_list(system_id, hot_upgrade)
    local valid_list = {}
    local file_dir = defs.UPGRADE_CPLD_VALID_FILE_DIR
    if hot_upgrade then
        file_dir = defs.HOT_UPGRADE_CPLD_VALID_FILE_DIR
    end
    if not vos.get_file_accessible(file_dir) then
        return valid_list
    end
    -- 遍历目录下所有valid文件
    for _, valid_name in pairs(utils_core.dir(file_dir)) do
        local file_info = {}
        local tb = {}
        -- 解析文件名valid_uid_index_sysid.vme，解析结果最长4段
        for v in string.gmatch(valid_name, '%w+') do
            tb[#tb + 1] = v
        end
        -- 兼容历史非4段式生效文件
        local file_sys_id = tonumber(tb[4])
        if not file_sys_id then
            file_sys_id = defs.SINGEL_SYSTEM_ID
        end
        if system_id ~= defs.ALL_SYSTEM_ID and system_id ~= file_sys_id then
            goto continue
        end
        file_info.path = string.format('%s/%s',
                                       file_dir,
                                       valid_name)
        file_info.file_type = valid_name:match('.*%.(.*)')
        file_info.uid = tb[2] -- 第2段表示组件uid
        file_info.chip_index = tonumber(tb[3]) -- 第3段表示chip_index
        file_info.system_id = file_sys_id -- 第4段表示system_id
        log:notice('[cpld]found %s, uid[%s], chip_index[%s], sysid[%s]', file_info.path,
            file_info.uid, file_info.chip_index, file_info.system_id)
        table.insert(valid_list, file_info)
        ::continue::
    end
    return valid_list
end

function valid:get_system_id_list(hot_upgrade)
    local system_id_list = {}
    local file_dir = defs.UPGRADE_CPLD_VALID_FILE_DIR
    if hot_upgrade then
        file_dir = defs.HOT_UPGRADE_CPLD_VALID_FILE_DIR
    end
    if not vos.get_file_accessible(file_dir) then
        return system_id_list
    end
    -- 遍历目录下所有valid文件
    for _, valid_name in pairs(utils_core.dir(file_dir)) do
        local tb = {}
        -- 解析文件名valid_uid_index_sysid.vme，解析结果最长4段
        for v in string.gmatch(valid_name, '%w+') do
            tb[#tb + 1] = v
        end
        -- 兼容历史非4段式生效文件
        local system_id = tonumber(tb[4]) -- 第4段表示system_id
        if not system_id then
            system_id = defs.SINGEL_SYSTEM_ID
        end
        log:notice('get_system_id_list sysid[%s]', system_id)
        system_id_list[system_id] = true
    end
    return system_id_list
end

function valid:remove_vaild_file_by_system_id(system_id, hot_upgrade)
    local file_dir = defs.UPGRADE_CPLD_VALID_FILE_DIR
    if hot_upgrade then
        file_dir = defs.HOT_UPGRADE_CPLD_VALID_FILE_DIR
    end
    if not vos.get_file_accessible(file_dir) then
        return
    end
    -- 遍历目录下所有valid文件
    local file_path
    for _, valid_name in pairs(utils_core.dir(file_dir)) do
        local tb = {}
        -- 解析文件名valid_uid_index_sysid.vme，解析结果最长4段
        for v in string.gmatch(valid_name, '%w+') do
            tb[#tb + 1] = v
        end
        -- 兼容历史非4段式生效文件
        local file_sys_id = tonumber(tb[4])
        if not file_sys_id then
            file_sys_id = defs.SINGEL_SYSTEM_ID
        end
        if system_id ~= file_sys_id then
            goto continue
        end
        file_path = string.format('%s/%s', file_dir, valid_name)
        log:notice('remove_vaild_file_by_system_id sysid[%s]', system_id)
        utils.remove_file(file_path)
        ::continue::
    end
end

local function make_cpld_upg_valid(system_id, fw_list, file, valid_list)
    for _, fw in ipairs(fw_list) do
        if fw.system_id == system_id and fw.uid == file.uid then
            fw:switch_to_firmware_route()
            local cpld_info = {}
            drivers:get_cpld_device_info(fw.chip_info, cpld_info)
            -- bypass偏移使能
            local ok, error = pcall(fw.chip_info.SetBypassMode, fw.chip_info,
                                    ctx.new(), true)
            if not ok then log:info('set_bypass_mode fail:%s', error) end
            -- 根据vme文件信息定位到对应chip
            drivers:set_upg_cpld_chip(fw.chip_info, file.chip_index - 1)
            set_cpld_validating_flag(fw, true)
            ok, error = pcall(function ()
                valid_cpld(fw, file.path, file.file_type)
            end)
            if not ok then log:error('valid cpld fail:%s', error) end
            set_cpld_validating_flag(fw, false)
            fw:switch_to_default_route()
            table.insert(valid_list, fw)
        end
    end
end

function valid:get_validating_flag(db, system_id)
    local fw_table = db.CpldValidating
    local property = fw_table({SystemId = system_id})
    local valid_flag = property.ValidatingCpldFlag == 1
    log:notice('[cpld]get system_id = %s ValidatingCpldFlag = %s', system_id, valid_flag)
    return valid_flag
end

function valid:set_validating_flag(db, system_id, status)
    log:notice('[cpld]set system_id = %s ValidatingCpldFlag = %s', system_id, status)
    local fw_table = db.CpldValidating
    local property = fw_table({SystemId = system_id})
    property.ValidatingCpldFlag = status
    property:save()
end

local function lock_internal(bus, sys_id, lock_time, is_lock, reason)
    return fructl.set_power_on_lock(bus, sys_id, is_lock, lock_time, reason, 'general_hardware')
end

local function lock_until_success(bus, sys_id, time, reason)
    local cnt = 0
    local res
    repeat
        res = lock_internal(bus, sys_id, time, true, reason)
        skynet.sleep(50)
        cnt = cnt + 1
    until res or cnt > 20
    if cnt > 20 then
        log:error('[cpld]lock power failed')
    end
end

function valid.cpld_hot_valid(system_id, signal)
    local valid_files = valid:get_valid_file_list(system_id, true)
    local valid_fw = {}
    for _, file in ipairs(valid_files) do
        make_cpld_upg_valid(system_id, signal.fw_list, file, valid_fw)
        -- 每生效1个文件等待500ms
        skynet.sleep(50)
        utils.remove_file(file.path)
    end
end

function valid.cpld_valid_task(signal, system_id)
    if not valid:get_validating_flag(signal.db, system_id) then
        log:notice('[cpld]no component need valid')
        return false
    end
    -- 生效阶段禁止上电，cpld生效流程约2分钟，设置300s后没有手动解锁则自动解锁
    lock_until_success(signal.bus, system_id, FRUCTL_LOCK_TIME, 'cpld')
    local valid_files = valid:get_valid_file_list(system_id, false)
    if next(valid_files) then
        local valid_fw = {}
        for _, file in ipairs(valid_files) do
            make_cpld_upg_valid(system_id, signal.fw_list, file, valid_fw)
            -- 每生效1个文件等待500ms，删除该生效文件
            skynet.sleep(50)
            utils.remove_file(file.path)
        end
        -- 硬件建议等待2s
        skynet.sleep(200)
    end
    -- 这里不需要解锁上电，防止powercycle状态误判，AC后锁会自动丢失
    valid:set_validating_flag(signal.db, system_id, 0)
    fructl.set_poweron_strategy_exceptions(signal.bus, 1)
    return true
end

return valid
