-- 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.


local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local cmn = require 'common'
local defs = require 'unit_manager.class.logic_fw.comm_defs'

local MAX_WAIT_FPGA_SELF_TEST <const> = 3
local SELF_TEST_PERIOD <const> = 18000
local self_test_succ_flag = nil

local fpga_self_test = {}
fpga_self_test.__index = fpga_self_test

function fpga_self_test.new(db, fw)
    return setmetatable({
        db = db,
        fw_list = fw
    }, fpga_self_test)
end

-- 检验cpld或FPGA升级状态
local function check_logic_fw_upgrading_flag()
    return defs.UPGRADING_CPLD == 1 or defs.UPGRADING_FPGA == 1
end

local function find_fpga_fw_obj(fw_list)
    for _, fw in ipairs(fw_list) do
        if fw.csr ~= nil and string.find(fw.csr.Name, 'FPGA') then
            log:notice('[Fpga] find fpga fw success')
            return fw
        end
    end
end

local function fpga_self_test_once(fw)
    local ok, val_read = pcall(function()
        return fw.csr.FpgaTestRegR
    end)
    if not ok or val_read == nil then
        return
    end
    local val_write = (val_read == 0xaa and 0xaa or 0x55)
    ok = pcall(function()
        fw.csr.FpgaTestRegW = val_write
    end)
    if not ok then
        log:info('[Unit] set FpgaTestReg object fail, error: %s', val_write)
        val_write = -1
    end
    cmn.skynet.sleep(50) -- 延时500ms防止写入没有生效
    ok, val_read = pcall(function()
        return fw.csr.FpgaTestRegR
    end)
    if not ok or val_read == nil then
        log:info('[Unit] get FpgaTestReg object fail, error: %s', val_read)
        val_read = -1
    end
    if val_write >= 0 and val_read >= 0 and val_read == (val_write == 0x55 and 0xaa or 0x55) then
        return true
    end

    log:info('[Unit] fpga scan %s, self test once failed. val_read val_write: %s %s',
        fw.id, val_read, val_write)
    return false, val_write, val_read
end

function fpga_self_test:get_fpga_self_test_result()
    log:notice('start fpga self test once')
    local fpga_fw_obj = find_fpga_fw_obj(self.fw_list)
    if not fpga_fw_obj then
        log:notice('not find fpga fw obj')
        return false
    end

    local count = 0
    while count < MAX_WAIT_FPGA_SELF_TEST do
        local result = fpga_self_test_once(fpga_fw_obj)
        if result == nil or result then
            return true
        end
        count = count + 1
        cmn.skynet.sleep(100)
    end
    log:notice('fpga self test once fail')
    return false
end

local function record_fpga_self_test_log(result)
    if self_test_succ_flag == nil then
        log:notice('fpga self test result is %s', result)
        self_test_succ_flag = false
        return
    end

    if result and not self_test_succ_flag then
        log:notice('fpga self test succ!')
        self_test_succ_flag = true
        return
    end

    if not result and self_test_succ_flag then
        log:notice('fpga self test fail!')
        self_test_succ_flag = false
        return
    end
end

local function start_fpga_self_test(fw)
    if check_logic_fw_upgrading_flag() then
        return
    end

    local result, val_write, val_read = fpga_self_test_once(fw)
    if result == nil then
        return
    end

    -- 记录自检日志
    record_fpga_self_test_log(result)
    -- 自检成功
    if result then
        fw.csr.FpgaCount = 0 -- 自检失败次数重置成0
        -- 默认配置成0, 表示没有告警
        return
    end

    -- 自检失败
    local count = fw.csr.FpgaCount
    if count < 12 then
        if val_write < 0 or val_read < 0 then -- FpgaStatus为U32类型，不可能读到-1
            -- 通讯异常失败次数上限12次，1min
            fw.csr.FpgaCount = count + 1
        elseif val_read ~= val_write then
            -- fpga内部错误次数上限3次，15s
            fw.csr.FpgaCount = count + 4
        end
    end

    count = fw.csr.FpgaCount
    if count >= 12 then -- 自检失败，更新状态
        log:info('[Unit] fpga scan %s, self test failed. val_read val_write: %s %s',
            fw.id, val_read, val_write)
    end
end

-- FPGA告警状态更新任务, 根据FPGA测试寄存器的情况进行设置, 默认配置成0, 表示没有告警
function fpga_self_test:task_update_fpga_status()
    cmn.skynet.fork(function()
        local fpga_fw_obj = find_fpga_fw_obj(self.fw_list)
        if not fpga_fw_obj then
            log:error('[Fpga] find fpga fw fail')
            return
        end

        log:notice('[Fpga] start task (FpgaStatus update) for period self test')
        while true do
            cmn.skynet.sleep(SELF_TEST_PERIOD)
            start_fpga_self_test(fpga_fw_obj)
        end
    end)
end

return singleton(fpga_self_test)
