-- 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 log = require 'mc.logging'
local vos = require 'utils.vos'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local bs = require 'mc.bitstring'
local chip = require 'unit_manager.class.logic_fw.cpld_chip'
local drivers = require 'unit_manager.class.logic_fw.upgrade.drivers_api'
local valid = require 'unit_manager.class.logic_fw.upgrade.valid'
local utils = require 'mc.utils'
local valid_handle = require 'unit_manager.class.logic_fw.upgrade.valid'
local mtd_prop = require 'macros.property_def'
local utils_core = require 'utils.core'
local client = require 'client'
local _, file_sec = pcall(require, 'utils.file')
local _, mtd_api = pcall(require, 'mtd.drv')
local _, spi_flash = pcall(require, 'libmgmt_protocol.bios.infrastructure.spi_flash')

local MAX_CPLD_UPDATE_FILE_NUM = 32 -- 8家厂商，4片为例
local process = {}

local g_success_list = {}

local function get_applicable_component_fw(system_id, cfg, component_fw_list)
    local update_obj_list = {}
    if not cfg.uid then
        log:error('unsupport firmware: uid not exist')
        return update_obj_list
    end
    -- 比较所有组件的UId是否跟该升级包支持的UId匹配
    for _, fw in ipairs(component_fw_list) do
        if (system_id == defs.ALL_SYSTEM_ID or system_id == fw.system_id) and
            cfg:check_fw_uid_exist(fw) then
            log:notice('cpld fw matched, system_id = %s, fw.uid = %s, fw.csr.Name = %s',
                system_id, fw.uid, fw.csr.Name)
            table.insert(update_obj_list, fw)
        end
    end
    return update_obj_list
end

-- 加载cpld,将升级文件拷贝到cpld flash中
local function load_cpld_single(obj_fw, upg_file, file_type)
    local CPLD_UPGRADE_LOAD_MODE = 0
    local file_name_len = #upg_file
    local MAX_CPLD_LEN = 0x300000 -- 驱动限制为3M
    local file_len = vos.get_file_length(upg_file)
    if (file_len > MAX_CPLD_LEN or file_len <= 0) and file_type ~= 'svf' then
        log:error('file len:0x%x(Max:0x%x) invalid!', file_len, MAX_CPLD_LEN)
        return defs.RET.ERR
    end
    log:notice('load file[0x%x]', file_len)
    local upg_info = {
        mode = CPLD_UPGRADE_LOAD_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 = upg_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!', upg_file)
    return defs.RET.OK
end

local function get_upg_valid_file_cnt(file_path, file_type)
    local valid_vme_path
    local cpld_vme_path
    local cnt = 0
    for i = 1, MAX_CPLD_UPDATE_FILE_NUM do
        valid_vme_path = string.format('%svalid%02d.%s', file_path, i, file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, i, file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then cnt = cnt + 1 end
    end
    log:notice('[cpld]upg file cnt = %s', cnt)
    return cnt
end

local function load_cpld_with_chip_id(logic_fw, cpld_info, upg_cfg, file_path, upgrade_list, hot_upgrade)
    local valid_vme_path
    local cpld_vme_path
    local file_id
    local success_times = 0
    local file_num = get_upg_valid_file_cnt(file_path, upg_cfg.file_type)
    local chip_id
    -- bypass偏移使能
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode,
                            logic_fw.chip_info, require'mc.context'.new(), true)
    if not ok then
        log:error('[cpld]set_bypass_mode fail:%s', error)
        return defs.RET.ERR
    end
    for _, i in pairs(upgrade_list) do
        chip_id = cpld_info[string.format('device%d_id', i)]
        file_id = chip:get_cpld_upgrade_file_id(chip_id, upg_cfg.chip_num, i)
        log:notice('[cpld]id = 0x%x, index = %s / %s, file_id = %s', chip_id, i,
                 upg_cfg.chip_num, file_id)
        -- cpld的chip序号从0开始
        drivers:set_upg_cpld_chip(logic_fw.chip_info, i - 1)
        -- cpld升级文件均为vme
        valid_vme_path = string.format('%svalid%02d.%s', file_path, file_id, upg_cfg.file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, file_id, upg_cfg.file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then
            log:notice('[cpld]update file id:%s', file_id)
            if load_cpld_single(logic_fw, cpld_vme_path, upg_cfg.file_type) == defs.RET.OK then
                -- 保存valid文件
                valid:init_valid_file(logic_fw, i, valid_vme_path, upg_cfg.file_type, hot_upgrade)
                success_times = success_times + 1
                table.insert(g_success_list, i)
            end
            skynet.sleep(20)
        end
    end
    -- 允许拆包升级，升级成功数和预期个数一样
    if success_times == #upgrade_list then
        log:notice('[cpld]upgrade cpld success.')
        return defs.RET.OK
    end
    log:error('[cpld]local cpld fail times[%s] file_num[%s] supplier_mode[%s]',
             success_times, file_num, upg_cfg.supplier_mode)
    return defs.RET.ERR
end

local function load_cpld_with_file_nums(logic_fw, cfg, file_path)
    local valid_vme_path
    local cpld_vme_path
    local success_times = 0
    -- bypass偏移去使能
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode,
                            logic_fw.chip_info, require'mc.context'.new(), false)
    if not ok then
        log:error('[cpld]set_bypass_mode fail:%s', error)
        return defs.RET.ERR
    end
    for i = 1, MAX_CPLD_UPDATE_FILE_NUM do
        valid_vme_path = string.format('%svalid%02d.%s', file_path, i, cfg.file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, i, cfg.file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then
            if load_cpld_single(logic_fw, cpld_vme_path, cfg.file_type) == defs.RET.OK then
                -- 保存valid文件
                valid:init_valid_file(logic_fw, i, valid_vme_path, cfg.file_type)
                success_times = success_times + 1
                table.insert(g_success_list, i)
            end
            skynet.sleep(20)
        end
        if success_times == cfg.chip_num then return defs.RET.OK end
    end
    return defs.RET.ERR
end

local function load_cpld_multi_supplier_mode(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    local cpld_info = {}
    local ok = drivers:get_cpld_device_info(logic_fw.chip_info, cpld_info)
    if ok == defs.RET.ERR then
        log:notice('[cpld]get cpld device info failed')
        return defs.RET.ERR
    end
    -- 支持CPLD多厂商升级归一方案：通过cpld的chip信息选片升级
    if (chip:check_has_pango(cpld_info) and cfg.chip_num > 1) or
        (cfg.supplier_mode == defs.CPLD_SUPPLIER_MODE.MIXED) then
        return load_cpld_with_chip_id(logic_fw, cpld_info, cfg, file_path, upgrade_list, hot_upgrade);
    end
    -- 兼容旧的成链升级包
    return load_cpld_with_file_nums(logic_fw, cfg, file_path)
end

local function load_cpld(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    if cfg.supplier_mode <= defs.CPLD_SUPPLIER_MODE.SINGLE then
        log:notice('[cpld]_____load_cpld_single_____')
        -- 单一厂商固件包归一
        local ret = load_cpld_single(logic_fw, file_path .. 'cpld.' .. cfg.file_type, cfg.file_type)
        if ret == defs.RET.OK then
            valid:init_valid_file(logic_fw, 1, file_path .. 'valid.' .. cfg.file_type, cfg.file_type)
        end
        return ret
    else
        log:notice('[cpld]_____load_cpld_multi_supplier_mode_____')
        return load_cpld_multi_supplier_mode(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    end
end

local function load_cpld_with_lock(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    local lock_time_out = 600
    local ok, ret_code
    while lock_time_out > 0 do
        ok, ret_code = logic_fw:update_chip_lock(require 'mc.context'.new(), 600)
        if ok and ret_code == 0 then
            local upgrade_ret = load_cpld(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
            -- 硬件总线解锁
            ok, ret_code = logic_fw:update_chip_unlock(require 'mc.context'.new())
            if not ok or ret_code ~= 0 then
                log:error("[jtag cpld upgrade] set lock status to 0 failed, ret code:%s", ret_code)
            end
            return upgrade_ret
        end
        log:debug("[jtag cpld upgrade] get chip lock failed")
        lock_time_out = lock_time_out - 1
        skynet.sleep(100)
    end

    log:error("[jtag cpld upgrade] upgrade failed because can not get chip lock")
    return defs.RET.ERR
end

local function record_logic_multi_supplier_running_log(name, upgrade_list)
    local upg_fw_name = string.gsub(name, "%d+$", "")
    for _, upg_fw in ipairs(upgrade_list) do
        for _, succ_fw in ipairs(g_success_list) do
            if upg_fw == succ_fw then
                log:running(log.RLOG_INFO, 'Upgrade %s Firmware successfully', upg_fw_name .. upg_fw)
                goto continue
            end
        end
        log:running(log.RLOG_ERROR, 'Upgrade %s Firmware failed', upg_fw_name .. upg_fw)
        ::continue::
    end
end

local function record_logic_upgrade_running_log(name, upgrade_list, one_fw_ret)
    if name and upgrade_list and #upgrade_list ~= 0 then
        return record_logic_multi_supplier_running_log(name, upgrade_list)
    end
    if one_fw_ret ~= defs.RET.OK then
        log:running(log.RLOG_ERROR, 'Upgrade %s Firmware failed', name)
        return
    end
    log:running(log.RLOG_INFO, 'Upgrade %s Firmware successfully', name)
end

function process:validate_upgarde_process(fw_list, cfg_list, file_path)
    if (not fw_list) or (not cfg_list) then
        log:error('firmware or cfg not exist')
        return false
    end
    if (not file_path) or (not vos.get_file_accessible(file_path)) then
        log:error('error file_path')
        return false
    end
    return true
end

function process:install_spi_driver(fpga_fw)
    if vos.get_file_accessible(mtd_prop.DEVNAME_MTD) then -- 首先检查当前是否存在已加载的设备（避免bios加载时误升级）
        error('[spi]device ' .. mtd_prop.DEVNAME_MTD .. 'already exists, spi driver can not be installed again')
    end
    self:set_fpga_flash(fpga_fw, defs.UPGRADE_MUX) -- cpld选通至bmc分路
    local mhz = string.format('g_clk_mhz=%s', mtd_prop.PERIOD3_MHZ)
    spi_flash.insmod_driver(mtd_prop.SPIKODRV, mhz, mtd_prop.DEVNAME_MTD, client, client.PProxyDriverInsmod) --加载spi驱动
    skynet.sleep(100)
    spi_flash.check_device_ready(mtd_prop.DEVNAME_MTD) -- 检查驱动加载情况
end

function process:set_fpga_flash(fpga_fw, route_type)
    if fpga_fw == nil then
        error('fw is nil')
    end
    local cnt = 0
    while cnt < mtd_prop.SET_MUX_RETRY do
        log:notice('set mux cnt %s times', cnt)
        local ok = pcall(function()
            fpga_fw.csr.Routes = route_type
        end)
        if ok then
            log:notice('set cpld mux to %s successfully', fpga_fw.csr.Routes)
            return
        end
        cnt = cnt + 1
        skynet.sleep(100)
    end
    error(string.format('set mux failed %s times', cnt))
end

function process:write_file_to_flash(fw, file_path, device_fd)
    local erase_length = fw.csr.FlashSizeKiB
    local write_length = utils_core.stat(file_path).st_size
    if write_length > erase_length then
        error(string.format('illegal write_length:%s, larger than erase_length:%s', write_length, erase_length))
    end
    local file_fd, err = file_sec.open_s(file_path, 'r')
    if not file_fd then
        error(string.format('[fpga]open fpga upgrade package failed: %s', err))
    end
    local ok
    ok, err = pcall(function()
        log:notice('[fpga] start erasing and writing flash')
        mtd_api.erase(device_fd, mtd_prop.FLASH_START_ADDRESS, erase_length)
        file_fd:seek('set', mtd_prop.FLASH_START_ADDRESS)
        local data = file_fd:read(write_length)
        mtd_api.write(device_fd, mtd_prop.FLASH_START_ADDRESS, data, write_length)
        log:notice('[fpga] writing finished and start to read')
        if data ~= mtd_api.read(device_fd, mtd_prop.FLASH_START_ADDRESS, write_length) then -- 升级后回读校验
            error('data read from flash differ from write')
        end
    end)
    utils.safe_close_file(file_fd, function()
        log:notice('[fpga] safe close file finished')
    end)
    if not ok then
        error(string.format('erase and write flash failed, failed info:%s', err))
    end
end

function process:extract_upgrade_file(file_path)
    local ok, release_path, fw_name = pcall(function()
        local s, e = string.find(file_path, '/.+/')
        local release_path = string.sub(file_path, s, e) -- 提取路径/dev/shm/upgrade/xxx
        local fw_name = string.sub(file_path, e + 1, string.len(file_path)) -- 提取文件名Firmware1
        return release_path, fw_name
    end)
    if ok then
        return release_path, fw_name
    end
    log:error('extract file path failed')
end

function process:find_fpga_fw_with_uid(fpga_signal, fw_name)
    local fpga_fw_list = {}
    for _, cfg in ipairs(fpga_signal.upg_cfg_list) do
        if cfg.name ~= fw_name then -- 找到与文件名匹配的cfg节点
            goto continue
        end
        local component_fw_list = get_applicable_component_fw(1, cfg, fpga_signal.fw_list) -- 通过uid找到组件
        if #component_fw_list == 0 then
            error(string.format('no match component: firmware uid = %s', cfg.uid))
        end
        for _, fw in ipairs(component_fw_list) do
            if string.find(fw.csr.Name, 'FPGA') then
                log:notice('[fpga] find fpga_fw successfully')
                table.insert(fpga_fw_list, fw)
            end
        end
        break
        ::continue::
    end
    if #fpga_fw_list == 0 then
        error('can not find fpga firmware')
    end

    return fpga_fw_list
end

function process:close_remove_driver_and_reset_mux(device_fd, fpga_fw)
    if device_fd then
        mtd_api.close(device_fd)
    end
    spi_flash.rmmod_driver(mtd_prop.SPIKODRV, mtd_prop.DEVNAME_MTD, client, client.PProxyDriverRmmod)
    self:set_fpga_flash(fpga_fw, defs.FPGA_MUX)
end

function process:initial_set_mux(fpga_signal)
    for _, fw in fpga_signal.fw_list do
        if fw.csr.Name == 'FPGA' and fpga_signal.fpga_upgrade_type == defs.UPGRADE_FPGA_BY_SFC then
            self:set_fpga_flash(fw, defs.FPGA_MUX)
        end
    end
end

function process:upgrade_fpga_by_flash(fpga_signal, file_path)
    local release_path, fw_name = self:extract_upgrade_file(file_path) --加载升级文件
    log:debug('release_path is %s, fw_name is %s', release_path, fw_name)

    local ok, fpga_fw_list = pcall(function()
        return process:find_fpga_fw_with_uid(fpga_signal, fw_name)
    end)
    if not ok then
        return defs.RET.ERR
    end

    utils.secure_tar_unzip(file_path, release_path, 0x6400000, 1024) -- 解压文件
    for _, fpga_fw in ipairs(fpga_fw_list) do
        ok = pcall(function()
            self:install_spi_driver(fpga_fw) -- cpld mux + spi驱动安装
        end)
        if not ok then
            log:error('[fpga]spi_driver install failed')
            pcall(function()
                process:close_remove_driver_and_reset_mux(nil, fpga_fw) -- 失败后卸载
            end)
            return defs.RET.ERR
        end

        local device_fd = mtd_api.open(mtd_prop.DEVNAME_MTD, mtd_prop.O_SYNC_RDWR)
        local dst_path = release_path .. 'fpga.bin'

        local write_ok = pcall(function()
            self:write_file_to_flash(fpga_fw, dst_path, device_fd) -- 将fpga.bin写入flash
        end)

        local close_ok = pcall(function()
            process:close_remove_driver_and_reset_mux(device_fd, fpga_fw) -- 升级后,无论成功、失败都需卸载设备及驱动
        end)

        if not write_ok or not close_ok then
            log:error('[fpga]write flash or close failed')
            return defs.RET.ERR
        end

        local active_ok = pcall(function()
            fpga_fw.csr.ValidAction = defs.ACTIVE_MUX -- 生效fpga
            log:notice('[fpga]set logicFirmware ValidAction: %s', defs.ACTIVE_MUX)
        end)
        if not active_ok then
            log:error('[fpga]active fpga failed')
            return defs.RET.ERR
        end
    end

    return defs.RET.OK
end

function process:upgrade_component_cpld(db, system_id, fw_list, cfg_list, file_path, upgrade_list, hot_upgrade)
    -- 输入路径/dev/shm/upgrade/Firmware
    local release_path, fw_name = self:extract_upgrade_file(file_path) --加载升级文件
    local one_fw_ret
    local final_ret = defs.RET.OK
    local is_need_valid = false
    for _, cfg in ipairs(cfg_list) do
        if cfg.name ~= fw_name then
            goto continue
        end
        -- 找到与文件名匹配cfg的节点
        -- 通过Uid找到适用的组件对象
        local component_fw_list = get_applicable_component_fw(system_id, cfg, fw_list)
        if #component_fw_list == 0 then
            log:error('no match component: firmware uid = %s', cfg.uid)
            return defs.RET.MATCH_FAIL, is_need_valid
        end
        -- uid适用情况下，解压在与Firmware同级文件夹
        utils.secure_tar_unzip(
            file_path,
            release_path,
            0x6400000, -- 最大解压限制100M
            1024
        )
        log:notice('[cpld]get cpld packages successful')
        for _, fw in ipairs(component_fw_list) do
            fw:switch_to_firmware_route()
            -- 启动升级
            g_success_list = {}
            one_fw_ret = load_cpld_with_lock(fw, cfg, release_path, upgrade_list, hot_upgrade)
            record_logic_upgrade_running_log(fw.name, upgrade_list, one_fw_ret)
            if one_fw_ret ~= defs.RET.OK then
                final_ret = one_fw_ret
            end
            -- 设置生效标志
            if one_fw_ret == defs.RET.OK and not hot_upgrade and not string.find(fw.csr.Name, 'FPGA') then
                valid_handle:set_validating_flag(db, fw.system_id, 1)
                is_need_valid = true
            end
            fw:switch_to_default_route()
        end
        -- 目前框架一次只发一个Firmware，使用完就可以退出了
        break
        ::continue::
    end
    return final_ret, is_need_valid
end

return process
