-- 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: 升级信号监听及回调处理
local skynet = require 'skynet'
local object_def = require "macros.object_def"
local client = require 'bios.client'
local log = require 'mc.logging'
local bios_factory = require 'factory.bios_factory'
local bios_enum = require 'bios.types.enums'
local active_enum = require 'domain.bios_firmware.defs'
local upgrade_service = require 'service.upgrade_service'
local context = require 'mc.context'
local vos = require 'utils.vos'
local alarm_manager = require 'domain.alarm.alarm_manager'
local boot_config_validate_service = require 'service.boot_config_activate_service'
local peripherals_device = require 'service.peripherals_device'
local bios_service = require 'service.bios_service'
local pro_global = require 'macros.property_global'
local boot_def = require "macros.boot_def"
local reset_lock = require 'infrastructure.reset_lock'
local fructl = require 'infrastructure.fructl'
local krun_service = require 'service.krun_service'

local FRUCTRL_ITF = 'bmc.kepler.Systems.FruCtrl'
local POWER_STATE_PROPERTY = 'PowerState'
local SYS_RESET_FLAG = 'SysResetDetected'
local UPGRADE_PATH<const> = '/data/upgrade'
local CACHED_BIOS_PKG_PATH<const> = UPGRADE_PATH .. '/bios.tar.gz'
local TEE_NAME<const> = 'Teeos'
local KRUN_NAME<const> = 'KRUN'

local CACHED_BIOS_REBOOT_AND_POWER_OFF_UPGRADE = 0x00 -- OS重启及下电均升级缓存的BIOS（默认）
local CACHED_BIOS_REBOOT_DONOT_UPGRADE = 0x01 -- 仅OS下电升级缓存的BIOS
local CACHED_BIOS_REBOOT_AND_POWER_OFF_DONT_UPGRADE = 0x02 -- OS重启及下电均不升级缓存的BIOS
local CACHED_BIOS_UPGRADE_MODE_MAX = 0x03

local REPLY_ERR = -1
local REPLY_OK = 0
local REPLY_NOT_SUPPORT = 38

local SYSTEM_ID<const> = 1

local upgrade_service_map = {
    [object_def.BIOS_NAME] = upgrade_service,
    ['BIOS'] = upgrade_service,
    [TEE_NAME] = upgrade_service,
    [object_def.KRUN_NAME] = krun_service,
    ['KRUN'] = krun_service
}

local UpgradeSignal = {}

local function get_bios_active_condition()
    local db = UpgradeSignal.db
    local record = db:select(
        db.CachedBiosUpgradeTable
    ):where(db.CachedBiosUpgradeTable.Id:eq(1)):first()
    return record and record.BiosActiveCondition or 'PowerOff'
end

function UpgradeSignal.listen_fructrl(bus, cb)
    UpgradeSignal.signal_slots[#UpgradeSignal.signal_slots + 1] = client:OnFruCtrlPropertiesChanged(cb)
end

function UpgradeSignal.power_state_changed_callback()
    local boot_options_ser = bios_factory.get_service('boot_options_service')
    log:notice('power state changed set_option_flag = %s, set_effective_flag = %s',
        pro_global.G_SET_BIOS_OPTION, pro_global.G_SET_BIOS_OPTION_FLAG)

    bios_service.get_instance():on_power_state_changed()

    if pro_global.G_SET_BIOS_OPTION_FLAG == boot_def.TaskFlag.LastRpc and
        pro_global.G_SET_BIOS_OPTION == boot_def.TaskFlag.LastRpc then
        boot_config_validate_service.get_instance():on_power_state_changed()
        boot_options_ser:on_option_power_state_changed()
        boot_options_ser:on_times_power_state_changed()
    elseif pro_global.G_SET_BIOS_OPTION_FLAG == boot_def.TaskFlag.LastIpmi and
        pro_global.G_SET_BIOS_OPTION == boot_def.TaskFlag.LastIpmi then
        boot_options_ser:on_option_power_state_changed()
        boot_options_ser:on_times_power_state_changed()
        boot_config_validate_service.get_instance():on_power_state_changed()
    elseif pro_global.G_SET_BIOS_OPTION_FLAG == boot_def.TaskFlag.LastRpc and
        pro_global.G_SET_BIOS_OPTION == boot_def.TaskFlag.LastIpmi then
        boot_options_ser:on_option_power_state_changed()
        boot_config_validate_service.get_instance():on_power_state_changed()
        boot_options_ser:on_times_power_state_changed()
    elseif pro_global.G_SET_BIOS_OPTION_FLAG == boot_def.TaskFlag.Begin and
        pro_global.G_SET_BIOS_OPTION == boot_def.TaskFlag.Begin then
       return
    else
        boot_options_ser:on_times_power_state_changed()
        boot_config_validate_service.get_instance():on_power_state_changed()
        boot_options_ser:on_option_power_state_changed()
    end
    pro_global.G_SET_BIOS_OPTION_FLAG = boot_def.TaskFlag.Begin
    pro_global.G_SET_BIOS_OPTION = boot_def.TaskFlag.Begin
end

local function active_register(is_multihost)
    local param = active_enum.BiosActiveRegisterList
    if is_multihost then
        param[3] = {Key = 'ActiveCondition', Value = 'ChassisPowerOff'}
    else
        param[3] = {Key = 'ActiveCondition', Value = get_bios_active_condition()}
    end
    client:FirmwareActiveFirmwareActiveRegisterActiveAction(context.new(), param)
end

function UpgradeSignal.init(bus, db)
    UpgradeSignal.db = db
    UpgradeSignal.activating = {}
    UpgradeSignal.slots = {}
    UpgradeSignal.signal_slots = {}
    client:SubscribeUpdateServiceUpdateServiceUpgradePrepareSignal(UpgradeSignal.upgrade_prepare_callback)
    client:SubscribeUpdateServiceUpdateServiceUpgradeProcessSignal(UpgradeSignal.upgrade_process_callback)
    client:SubscribeUpdateServiceUpdateServiceUpgradeFinishSignal(UpgradeSignal.upgrade_finish_callback)
    client:SubscribeFirmwareActiveFirmwareActiveActiveProcessSignal(UpgradeSignal.active_process_callback)
    -- 生效的兼容性考虑,存在升级包则注册信号
    if vos.get_file_accessible(CACHED_BIOS_PKG_PATH) then
        local upgrade_ser = upgrade_service.get_instance()
        -- 只有bios生效模式不为OS重启及下电均不升级缓存的BIOS时，才向固件管理注册
        if upgrade_ser:get_cached_bios_upgrade_mode() < CACHED_BIOS_REBOOT_AND_POWER_OFF_DONT_UPGRADE then
            skynet.fork_once(function ()
                skynet.sleep(10 * 100)
                local is_multihost = UpgradeSignal.is_multihost()
                log:notice('[bios] start to register activate signal')
                active_register(is_multihost)
            end)
        end
    end
    -- 监听预上电信号
    client:SubscribeFruCtrlFruCtrlBeforePowerOnSignal(UpgradeSignal.verify_flash)
    log:notice('[bios]subscribe before power on signal success')
    UpgradeSignal.listen_fructrl(bus, UpgradeSignal.activate_bios_fw_callback)
    log:notice('[bios]subscribe fructrl signal success')
    client:OnFruCtrlPropertiesChanged(function(values, path)
        if values[POWER_STATE_PROPERTY] or values[SYS_RESET_FLAG] then
            local system_id = tonumber(string.match(path or '', '/bmc/kepler/Systems/(%d+)/FruCtrl'))
            if system_id ~= active_enum.SINGLE_HOST_SYSTEM_ID or
                fructl.get_power_restart_cause(system_id) ~= 'ChassisControlCommand' then
                return
            end
            UpgradeSignal.power_state_changed_callback()
        end
    end)
end

local function get_krun_version(system_id)
    local krun_ser = bios_factory.get_service('krun_service')
    if not krun_ser then
        return '0.00'
    end
    local version = krun_ser:get_krun_prop('Version', system_id)
    log:notice('[krun] prepare get system %s verion %s', system_id, version)
    if not version then
        version = '0.00'
    end
    return version
end

local function get_version(system_id, firmware_type)
    if firmware_type == object_def.KRUN_NAME then
        return get_krun_version(system_id)
    end
    local bios_ser = bios_factory.get_service('bios_service')
    if not bios_ser then
        return '0.00'
    end
    if system_id == active_enum.ALL_HOST_SYSTEM_ID then
        return ''
    end
    local version = bios_ser:get_bios_prop('Version', system_id)
    log:notice('[bios] prepare get system %s verion %s', system_id, version)
    if not version then
        version = '0.00'
    end
    return version
end

function UpgradeSignal.prepare_upgrade(cfg, parameters)
    local upgrade_ser = upgrade_service_map[cfg.firmware_type].get_instance()
    local status = REPLY_OK
    local ok, err = pcall(function()
        upgrade_ser:upgrade_hpm('prepare', cfg)
    end)
    if not ok then
        log:error('[bios]prepare upgrade fail, err %s', err)
        status = REPLY_ERR
    end
    client:PUpdateServiceUpdateServicePrepareReply(context.new(),
        cfg.system_id, cfg.firmware_type, get_version(cfg.system_id, cfg.firmware_type), status, parameters)
end

function UpgradeSignal.upgrade_prepare_callback(ctx, system_id, firmware_type, cfg_path, hpm_path, parameters)
    if firmware_type ~= object_def.BIOS_NAME and firmware_type ~= TEE_NAME and firmware_type ~= object_def.KRUN_NAME then
        return
    end
    log:notice("Start the %s upgrade prepare phase", firmware_type)
    log:notice(parameters)

    local bios_ser = bios_factory.get_service('bios_service')
    if bios_ser.dft_mode and parameters and parameters.UpgradeMode and
        parameters.UpgradeMode == 'Parallel' then
        log:notice('[%s] dft mode is true, not support parallel upgrade', firmware_type)
        client:UpdateServiceUpdateServicePrepareReply(context.new(),
            system_id, firmware_type, '', REPLY_NOT_SUPPORT, parameters)
        return
    end

    if parameters and parameters.Action then
        UpgradeSignal.action = parameters.Action
    else
        UpgradeSignal.action = nil
    end
    local ok, rsp = client:PFileFileChown(context.new(), nil, cfg_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[%s] chown cfg file failed, error %s', firmware_type, rsp)
        client:UpdateServiceUpdateServicePrepareReply(context.new(),
            system_id, firmware_type, '', REPLY_ERR, parameters)
        return
    end

    ok, rsp = client:PFileFileChown(context.new(), nil, hpm_path, 104, 104)  --设置升级文件属主为secbox
    if not ok then
        log:error('[%s] chown hpm file failed, error %s', firmware_type, rsp)
        client:UpdateServiceUpdateServicePrepareReply(context.new(),
            system_id, firmware_type, '', REPLY_ERR, parameters)
        return
    end

    skynet.fork_once(function()
        local cfg = {
            system_id = system_id,
            firmware_type = firmware_type,
            cfg_path = cfg_path,
            hpm_path = hpm_path,
            context = ctx,
            para = parameters
        }
        UpgradeSignal.prepare_upgrade(cfg, parameters)
    end)
end

function UpgradeSignal.process_upgrade(cfg, para_tab)
    local upgrade_ser = upgrade_service_map[cfg.firmware_type].get_instance()
    local status = REPLY_OK
    pcall(function()
        client:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), cfg.system_id,
            cfg.firmware_type, 0, 50, 'Running', para_tab)
    end)
    local ok, err = pcall(function()
        upgrade_ser:upgrade_hpm('process', cfg)
    end)
    if not ok then
        log:error('[bios]process upgrade fail, err %s', err)
        status = REPLY_ERR
    end
    if cfg.firmware_type ~= object_def.KRUN_NAME then
        local bios_ser = bios_factory.get_service('bios_service')
        bios_ser:clear_activate_mode(SYSTEM_ID)
    end
    client:PUpdateServiceUpdateServiceProcessReply(context.new(),
        cfg.system_id, cfg.firmware_type, status, para_tab)
end

function UpgradeSignal.upgrade_process_callback(ctx, system_id, firmware_type, file_path, para_tab)
    if firmware_type ~= object_def.BIOS_NAME and firmware_type ~= TEE_NAME and firmware_type ~= object_def.KRUN_NAME then
        return
    end
    local ok, rsp = client:PFileFileChown(context.new(), nil, file_path, 104, 104)
    if not ok then
        log:error('[%s] chown file failed, error %s', firmware_type, rsp)
        client:PUpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, REPLY_ERR, para_tab)
        return
    end

    log:notice("Start the %s upgrade process phase", firmware_type)
    local bios_ser = bios_factory.get_service('bios_service')
    skynet.fork_once(function()
        local cfg = {
            system_id = system_id,
            firmware_type = firmware_type,
            file_path = file_path,
            is_online_force = bios_ser:is_online_force(SYSTEM_ID),
            ActivateComponents = bios_ser:get_activate_mode(SYSTEM_ID),
            context = ctx,
            action = UpgradeSignal.action
        }
        UpgradeSignal.process_upgrade(cfg, para_tab)
    end)
end

function UpgradeSignal.finish_upgrade(cfg, para_tab)
    local upgrade_ser = upgrade_service_map[cfg.firmware_type].get_instance()
    local status = REPLY_OK
    local ok, err = pcall(function()
        upgrade_ser:upgrade_hpm('finish', cfg)
    end)
    if not ok then
        log:error('[bios]finish upgrade fail, err %s', err)
        status = REPLY_ERR
    end
    client:PUpdateServiceUpdateServiceFinishReply(context.new(),
        cfg.system_id, cfg.firmware_type, status, para_tab)
    upgrade_ser:judge_active_register(cfg.system_id)
    if upgrade_ser.set_power_cycle_after_finish then
        upgrade_ser:set_power_cycle_after_finish(cfg)
    end
end

function UpgradeSignal.upgrade_finish_callback(ctx, system_id, firmware_type, para_tab)
    if firmware_type ~= object_def.BIOS_NAME and firmware_type ~= TEE_NAME and firmware_type ~= object_def.KRUN_NAME then
        return
    end
    log:notice("Start the %s upgrade finish phase", firmware_type)
    skynet.fork_once(function()
        local cfg = {
            system_id = system_id,
            firmware_type = firmware_type,
            context = ctx,
            action = UpgradeSignal.action
        }
        UpgradeSignal.finish_upgrade(cfg, para_tab)
    end)
end

local function get_system_id(path)
    return tonumber(string.match(path, '/bmc/kepler/Systems/(%d+)'))
end

local function clear_system_info(system_id)
    local boot_ser = bios_factory.get_service('boot_service')
    if not boot_ser then
        log:error('clear system info failed, boot_service is nil')
        return
    end
    boot_ser:clear_system_info(system_id)
end

local function init_status(system_id)
    log:notice('[bios]init system(%s) status', system_id)
    local smbios_ser = bios_factory.get_service('smbios_service')
    smbios_ser:update_smbios_status(bios_enum.SmbiosWriteStage.SMBIOS_WRITE_NOT_START, system_id)
    local bios_ser = bios_factory.get_service('bios_service')
    bios_ser:set_prop('SystemStartupState', 0, system_id)
    bios_ser:set_prop('BiosBootStage', 0, system_id)
    pcall(function()
        bios_ser:bios_registry_version_update(system_id)
        bios_ser:async_fetch_info(system_id)
        -- 写ipmi属性
        local objects = client:GetWatchdog2StatusObjects()
        for _, obj in pairs(objects) do
            if get_system_id(obj.path) == system_id then
                obj.SystemStartupState = 0
                break
            end
        end
    end)
    -- 清除告警
    local alarm_mgr = alarm_manager.get_instance()
    alarm_mgr:recover(system_id)
    clear_system_info(system_id)
end

local function check_secure_boot_verify()
    -- 延时60s,确保bios已经更新了安全启动状态信息
    skynet.sleep(60 * 100)

    local bios_ser = bios_factory.get_service('bios_service')
    if not bios_ser then
        return
    end
    local verify_result = bios_ser:check_secure_boot_verify()
    bios_ser:secure_boot_alarm(verify_result)

    if not verify_result then
        local ok, err = pcall(function()
            local pfr_ser = bios_factory.get_service('pfr_service')
            return pfr_ser:secure_boot_verify_and_recover()
        end)
        if not ok then
            log:error('bios secure boot verify and recover failed err = %s', err)
        end
    end
end

local function update_active_status(firmware_id, firmware_type, status)
    log:notice('[%s]update active status', firmware_type)
    local param = {}
    param[#param+1] = {Key = 'FirmwareId', Value = firmware_id}
    param[#param+1] = {Key = 'FirmwareType', Value = firmware_type}
    param[#param+1] = {Key = 'ActiveStatus', Value = status}
    client:FirmwareActiveFirmwareActiveUpdateActiveStatus(context.new(), param)
end

function UpgradeSignal.power_off_effective_process(ctx, system_id, firmware_type, upgrade_ser)
    if UpgradeSignal.activating[firmware_type] then
        log:notice('[%s]activate processing return', firmware_type)
        return
    end
    log:notice('[%s]----------Start Active----------', firmware_type)
    UpgradeSignal.activating[firmware_type] = true
    local ok = pcall(function ()
        update_active_status(firmware_type, firmware_type, 'Apply')
        upgrade_ser:power_off_effective(system_id)
    end)
    pcall(function()
        log:notice('[%s]----------END Active----------', firmware_type)
        skynet.sleep(10 * 100)
        client:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(),
            system_id, firmware_type, ok and REPLY_OK or REPLY_ERR)
    end)
   UpgradeSignal.activating[firmware_type] = false
end

function UpgradeSignal.active_process_callback(ctx, system_id, firmware_type)
    if firmware_type ~= 'BIOS' and firmware_type ~= KRUN_NAME then
        return
    end
    -- 如果bios生效模式为OS重启及下电均不升级缓存的BIOS，则此处不去生效
    local upgrade_ser = upgrade_service_map[firmware_type].get_instance()
    if upgrade_ser.get_cached_bios_upgrade_mode and
        upgrade_ser:get_cached_bios_upgrade_mode() == CACHED_BIOS_REBOOT_AND_POWER_OFF_DONT_UPGRADE then
        return
    end

    skynet.fork_once(function()
        UpgradeSignal.power_off_effective_process(ctx, system_id, firmware_type, upgrade_ser)
    end)
end

function UpgradeSignal.is_multihost()
    local multihost = false
    client:ForeachMultihostObjects(function(obj)
        if obj.HostType == 'Multihost' then
            multihost = true
        end
    end)
    log:notice('[bios]multihost is %s', multihost)
    return multihost
end

function UpgradeSignal.activate_bios_fw_callback(values, path, interface)
    if interface ~= FRUCTRL_ITF then
        log:debug('[bios] monitor power state: interface(%s) mismatch', interface)
        return
    end
    local system_id = tonumber(string.match(path or '', '/bmc/kepler/Systems/(%d+)/FruCtrl'))
    -- 当前OS状态是下电状态启动生效
    if values[POWER_STATE_PROPERTY] and values[POWER_STATE_PROPERTY]:value() == 'OFF' then
        pcall(function()
            log:notice('[bios]monitor power state off: init bios status')
            init_status(system_id)
            pcall(function()
                peripherals_device.get_instance():recover_device_status(system_id)
            end)
            if not UpgradeSignal.is_multihost() then
                bios_factory.get_service('pfr_service'):try_unlock_forever()
            end
            pcall(function()
                upgrade_service.get_instance():clear_slave_upgrade(system_id)
            end)
            UpgradeSignal.unlock_bmc(system_id)
        end)
    -- 检测到重启信号则需要设置前置动作为powercycle
    elseif values[SYS_RESET_FLAG] and values[SYS_RESET_FLAG]:value() == 1 then
        pcall(function()
            log:notice('[bios]monitor power state: the power state is PowerCycle')
            init_status(system_id)
            UpgradeSignal.lock_bmc(system_id)
            if UpgradeSignal.is_multihost() then
                log:notice('[bios]mutihost, not verify')
                return
            end
            skynet.fork_once(function()
                check_secure_boot_verify()
            end)
        end)
        -- 如果bios生效模式不是OS重启及下电均升级缓存的BIOS，则此处重启不生效bios
        local upgrade_ser = upgrade_service.get_instance()
        if upgrade_ser:get_cached_bios_upgrade_mode() ~= CACHED_BIOS_REBOOT_AND_POWER_OFF_UPGRADE then
            log:notice('bios upgrade mode is not CACHED_BIOS_REBOOT_AND_POWER_OFF_UPGRADE')
            return
        end
        -- 定制bios生效条件为PowerCycle时，上电不触发PowerCycle
        if get_bios_active_condition() == 'PowerCycle' then
            log:notice('bios active condition is PowerCycle')
            return
        end

        skynet.fork_once(function()
            upgrade_ser:power_cycle_effective(system_id)
        end)
    else
        log:info('[bios]monitor power state: power state no changed')
    end
end

function UpgradeSignal.lock_bmc(system_id)
    pcall(function()
        local reset_lock_instance = reset_lock.get_instance()
        reset_lock_instance:lock(system_id)
    end)
end

function UpgradeSignal.unlock_bmc(system_id)
    pcall(function()
        local reset_lock_instance = reset_lock.get_instance()
        reset_lock_instance:unlock(system_id)
    end)
end

function UpgradeSignal.verify_flash(timeout)
    log:notice('[bios] bios receive before power on UpgradeSignal.')
    if UpgradeSignal.is_multihost() then
        log:notice('[bios]multihost no need verify')
        return
    end
    -- 清除告警
    skynet.fork_once(function()
        local alarm_mgr = alarm_manager.get_instance()
        alarm_mgr:recover()
    end)
end

return UpgradeSignal