-- 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 cjson = require 'cjson'
local client = require 'general_hardware.client'
local context = require 'mc.context'

local MAX_WAIT_FPGA_SELF_TEST <const> = 3
local DELAY_SELF_TEST_TIME <const> = 18000 --延迟3分钟等待fpga加载完成
local FPGA_SELF_TEST_INTERVAL <const> = 3000
local SELF_TEST_GOOD <const> = 0
local SELF_TEST_BAD <const> = 1
local self_test_succ_flag = nil

local EVENTS_PATH<const> = '/bmc/kepler/Systems/1/Events'

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,
        event_status = -1
    }, 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 -1
    end

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

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

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

local function generate_event(param)
    local event_obj
    client:ForeachEventsObjects(function(o)
        if o.path == EVENTS_PATH then
            event_obj = o
        end
    end)
    if not event_obj then
        log:error('[general_hardware]get events object failed')
        return false
    end
    local record
    local ok, res = pcall(function ()
        record = event_obj:AddEvent_PACKED(context.new(), param):unpack()
    end)
    if not ok then
        log:error('[general_hardware]generate fpga self-test event fail, %s', res)
        return false
    end
    log:info('[general_hardware]generate fpga self-test event(%s) successfully', record)
    return true
end

function fpga_self_test:generate_fpga_self_test_event(assert, device_name)
    if assert == self.event_status then
        return
    end
    local exp_slot = string.match(device_name, 'ExpBoard(%d)')
    local param = {
        {'ComponentName', device_name},
        {'State', assert == 1 and 'true' or 'false'},
        {'EventKeyId', 'ExpBoard.ExpBoardFPGASelfTestFailure'},
        {'MessageArgs', cjson.encode({"L1", exp_slot})},
        {'SystemId', ''},
        {'ManagerId', '1'},
        {'ChassisId', '1'},
        {'NodeId', ''}
    }
    local res = generate_event(param)
    if res == true then
        self.event_status = assert
    end
end

-- FPGA告警状态更新任务, 根据FPGA测试寄存器的情况进行设置, 默认配置成0, 表示没有告警
function fpga_self_test:task_update_fpga_status(device_name)
    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
        -- 等待fpga加载完成再开始自检
        cmn.skynet.sleep(DELAY_SELF_TEST_TIME)
        log:notice('[Fpga] start task (FpgaStatus update) for period self test')
        while true do
            cmn.skynet.sleep(FPGA_SELF_TEST_INTERVAL)
            local res = start_fpga_self_test(fpga_fw_obj)
            if res == SELF_TEST_GOOD or res == SELF_TEST_BAD then
                self:generate_fpga_self_test_event(res, device_name)
            end
        end
    end)
end

return singleton(fpga_self_test)
