-- 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 bs = require 'mc.bitstring'
local client = require 'general_hardware.client'
local ctx = require 'mc.context'
local log = require 'mc.logging'
local parser = require 'libmgmt_protocol'
local std_smbus_cfg = require 'protocol.std_smbus'
local context = require 'mc.context'
local skynet = require 'skynet'
local mdb = require 'mc.mdb'
local file_sec = require 'utils.file'
local mc_utils = require 'mc.utils'
local utils_core = require 'utils.core'
local log_collector = require 'log_collector'
local utils = require 'dpu_service.utils'
local add_event = require 'add_event'
local custom_messages = require 'messages.custom'
local dpu_enums = require 'dpu_service.dpu_enums'
local cjson = require 'cjson'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local event = require 'infrastructure.event'
local vos = require 'utils.vos'

local dpu_object = {
    fetch_pxe_option_task = false,
    fetch_power_failed_switch = true,
    fetch_secure_boot_switch = true,
    fetch_LLDP_status_switch = true
}
dpu_object.__index = dpu_object

local DPUCARD_INTF<const> = 'bmc.kepler.Systems.DPUCard'
local DPUCARD_FAULT_INTF<const> = 'bmc.kepler.Systems.DPUCard.Fault'
local DPUCARD_METRICS_INTF<const> = 'bmc.kepler.Systems.DPUCard.Metrics'
local DPUCARD_PROCESSOR_INTF<const> = 'bmc.kepler.Systems.DPUCard.Processor'
local DPUCARD_SYSTEM_INTF<const> = 'bmc.kepler.Systems.DPUCard.System'
local DPUCARD_INVENTORY_INTF<const> = 'bmc.kepler.Inventory.Hardware'
local PCIECARD_INTF<const> = 'bmc.kepler.Systems.PCIeDevices.PCIeCard'
local SERIAL_MGMT_PATH<const> = '/bmc/kepler/Managers/1/SerialManagement'
local SERIAL_MGMT_INTF<const> = 'bmc.kepler.Managers.SerialManagement'
local CONFIG_OS<const> = 1
local CHIP_BUFFER_LEN<const> = 32
local TLV_OS_STATUS<const> = 6
local TLV_POWER_STATUS<const> = 1 -- 获取DPU卡电源状态
local TLV_BIOS_STATUS<const> = 4
local SOL_SWITCH_INDEX<const> = 5
local LOG_DUMP_BUSY<const> = 1 -- 1表示正在收集
local LOG_DUMP_IDLE<const> = 0 -- 0 表示空闲
local PCIE_CARD_SUBJECT_TYPE<const> = 0x08
local CABLE_SUBJECT_TYPE<const> = 0x28
local FUNCTION_CLASS_FOR_DPU<const> = 11
local LOG_DUMP_MAX_TIME<const> = 120 -- sdi收集日志时间不超过2分钟

local PCIE_SENSOR_INVALID_READING<const> = 0x7fff
local PCIE_SENSOR_NA_READING<const> = 0x7ffd
local SENSOR_INVALID_READING<const> = 0x8000
local SENSOR_NA_READING<const> = 0x4000
local BIOS_LOG_TYPE = {
    MRC     = 1,
    UEFI    = 2
}
local SERIAL_RECORD_CONNECT = {
    Source         = 1,
    Destination    = 2,
    SrcSerial      = 3,
    DestSerial     = 4
}
local BIOS_LOG_LEVEL = {
    [1] = 'SIMPLE',
    [2] = 'FULL'
}
local BIOS_LOG_TYPE_STR = {
    MRCLogLevel = "MRC",
    UEFILogLevel = "UEFI"
}
local DEV_PRESENCE_ID = {
    SLIMLINE = 1,
    NCSI = 2,
    SSD1 = 3,
    SSD2 = 4,
    SFP1 = 5,
    SFP2 = 6,
    SFP3 = 7,
    SFP4 = 8
}

local POWER_STATE_TO_STR = {
    [0] = 'Off',
    [1] = 'On'
}

local prop_map = {
    CPUTemperatureCelsius = 'CPU',
    SFP1TemperatureCelsius = 'SFP1',
    SFP2TemperatureCelsius = 'SFP2',
    Inlet1TemperatureCelsius = 'Inlet1',
    Outlet1TemperatureCelsius = 'Outlet1',
    NetworkAdapterChipTemperatureCelsius = 'Gemini'
}

local function dal_addr_pack(data)
    if data == nil or data == '' then
        log:notice('The data is empty, converted to the default value 0.0.0.0')
        data = '0.0.0.0'
    end
    local pack_data = ''
    pcall(string.gsub(data, '[^.]+', function(val)
        pack_data = pack_data .. string.char(tonumber(val))
    end))
    return pack_data
end

local function dal_addr_ipv6_pack(data)
    if data == nil or data == '' then
        log:notice('The data is empty, converted to the default value 0000:0000:0000:0000:0000:0000:0000:0000')
        data = '0000:0000:0000:0000:0000:0000:0000:0000'
    end
    local pack_data = ''
    pcall(string.gsub(data, '[^:]+', function(val)
        for i = 1, #val, 2 do
            local hex_byte = string.sub(val, i, i + 1)
            pack_data = pack_data .. string.char(tonumber(hex_byte, 16))
        end
    end))
    return pack_data
end

local function get_std_smbus(ref_chip)
    return std_smbus_cfg.new(ref_chip, CHIP_BUFFER_LEN)
end

local function init_inventory(obj)
    pcall(function ()
        obj.AssetType = 'SDI Card'
        obj.AssetName = string.format('PCIeCard%s', obj.SlotID)
        obj.InventorySerialNumber = obj.SerialNumber
        obj.InventoryPCBVersion = obj.PCBVersion
        obj.InventoryManufacturer = obj.Manufacturer
        obj.InventoryPartNumber = obj.PartNumber
        obj.Slot = tostring(obj.SlotID)
        obj.InventoryModel = obj.Model
        obj.InventoryFirmwareVersion = obj.FirmwareVersion
        obj[PCIECARD_INTF].property_changed:on(function(name, value)
            if name == 'SerialNumber' then
                obj.InventorySerialNumber = value
            end
            if name == 'FirmwareVersion' then
                obj.InventoryFirmwareVersion = value
            end
        end)
    end)
end

function dpu_object.new(obj, position)
    if obj['RefWPChip'] then
        local ok, err = pcall(function ()
            obj['RefWPChip']:PluginRequest(context.new(), 'general_hardware',
                'rewrite_protect_chip', skynet.packstring())
        end)
        if not ok then
            log:error('[DPU]PluginRequest failed, error :%s', err)
        end
    end
    init_inventory(obj)
    return setmetatable({
        dpucard = obj[DPUCARD_INTF], dpucard_metrics = obj[DPUCARD_METRICS_INTF],
        dpucard_fault = obj[DPUCARD_FAULT_INTF], dpucard_processor = obj[DPUCARD_PROCESSOR_INTF],
        dpucard_system = obj[DPUCARD_SYSTEM_INTF], dpucard_inventory = obj[DPUCARD_INVENTORY_INTF],
        heartbeat_debounce = 0, mcu_reset_time = 10, power_read_fail_timestamp = 0,
        pciecard = obj[PCIECARD_INTF], ref_chip = obj['RefChip'], lock_chip = obj['LockChip'],
        ref_id_chip = obj['RefIdChip'], ref_dpu_frudata = obj['RefFrudata'],
        inner = obj, std_smbus = get_std_smbus(obj['RefChip']),
        dpu_position = position, mcu_fw_id = string.format('MCU_DPU_%s', position),
        cpld_fw_id = string.format('CPLD_DPU_%s', position), cpld1_fw_id = string.format('CPLD1_DPU_%s', position),
        cpld2_fw_id = string.format('CPLD2_DPU_%s', position), vrd_fw_id = string.format('DPU_VRD_%s', position),
        -- "-"为占位符，通过厂商ID替换
        mcu_fw_raw = {
            f1 = 'mcu_-.bin', -- 大于等于6.0
            f2 = string.format('%x_-.bin', obj['BoardID']), -- 5.0
        },
        cpld_fw_raw = {
            f1 = 'cpld_-.vme', -- 大于等于6.0
            f2 = string.format('cpld_%x_-.vme', obj['BoardID']), -- 5.0
            f3 = string.format('cpld_%x_-_00.vme', obj['BoardID']),
            f4 = string.format('cpld_%x_-_01.vme', obj['BoardID'])},
        vrd_fw_raw = {
            f1 = 'vrd_00.hex', -- 只有一个厂商，固定名称
            f2 = string.format('vrd_%x.hex', obj['BoardID'])},
        mcu_err_list = {}, component_dpucard = nil, uid = obj['UID'],
        firmware_name = '', exact_conn = {}, mutex_conn = {}, dpu_type = ''
    }, dpu_object)
end

function dpu_object:get_description()
    return self.pciecard['Description']
end

-- MRCLogLevel、UEFILogLevel属性变化回调函数
local function change_bios_log_level(obj, name, value)
    local cur_ctx = ctx.get_context()
    if not BIOS_LOG_LEVEL[value] then
        log:error("Bios LogLevel value is invalid, value: %s", value)
        return false
    end
    local data = obj:set_bios_log_level(BIOS_LOG_TYPE[BIOS_LOG_TYPE_STR[name]], value)
    if not data then
        log:error("Failed to set BIOS %s log level of DPU card %s to %s",
            BIOS_LOG_TYPE_STR[name],
            obj:get_slot_id(), BIOS_LOG_LEVEL[value])
        log:operation(cur_ctx:get_initiator(), "general_hardware",
            "Failed to set BIOS %s log level of DPU card %s to %s", BIOS_LOG_TYPE_STR[name],
            obj:get_slot_id(), BIOS_LOG_LEVEL[value])
        return false
    end
    log:operation(cur_ctx:get_initiator(), "general_hardware",
        "Set BIOS %s log level of DPU card %s to %s successfully", BIOS_LOG_TYPE_STR[name],
        obj:get_slot_id(), BIOS_LOG_LEVEL[value])
    return true
end

-- SecureBoot属性变化回调函数
local function change_secure_boot(obj, name, value)
    local cur_ctx = ctx.get_context()
    local log_value = dpu_enums.secure_boot_table[value]
    if not obj:is_dpu_card() then
        log:error("Only DPU Card can set secure boot value, slot:%s, value:%s",
            obj:get_slot_id(), log_value)
        log:operation(cur_ctx:get_initiator(), "general_hardware",
            "Set the Secure Boot of PCIeCard%s(%s) to %s failed", obj:get_slot_id(), obj.pciecard['Name'], log_value)
        error(custom_messages.OperationFailed())
    end
    local ok = obj:set_secure_boot(value)
    if not ok then
        log:error("Set the SDI secure boot failed, slot:%s, value:%s",
            obj:get_slot_id(), log_value)
        log:operation(cur_ctx:get_initiator(), "general_hardware",
            "Set the Secure Boot of PCIeCard%s(%s) to %s failed", obj:get_slot_id(), obj.pciecard['Name'], log_value)
        error(custom_messages.OperationFailed())
    end
    log:operation(cur_ctx:get_initiator(), "general_hardware",
        "Set the Secure Boot of PCIeCard%s(%s) to %s successfully", obj:get_slot_id(), obj.pciecard['Name'], log_value)
    return true
end

-- SystemLoadedStatus属性变化回调函数
local function change_system_loaded_status(obj, name, value)
    -- OS启动完成
    if value == 3 then
        local slot = obj:get_slot_id()
        local version = obj:fetch_bios_version()
        local mem_size = obj:fetch_mem_size()
        local cpu_arch = obj:fetch_cpu_arch()
        local cpu_cores = obj:fetch_cpu_cores()
        local disk_size = obj:fetch_disk_size()
        log:notice('SDI OS boot (slot:%s) suc, refesh version(%s), mem_size(%s),\
            cpu_arch(%s), cpu_cores(%s), disk_size(%s)', slot, version, mem_size, cpu_arch, cpu_cores, disk_size)
    end
    return true
end
dpu_object.mock_change_system_loaded_status = change_system_loaded_status

local change_property_map = {
    MRCLogLevel = change_bios_log_level,
    UEFILogLevel = change_bios_log_level,
    SecureBootOptionEnabled = change_secure_boot,
    SystemLoadedStatus = change_system_loaded_status
}

function dpu_object:add_prop_before_change_callback()
    self.dpucard.property_before_change:on(function(name, value, sender)
        if not sender and name ~= 'SystemLoadedStatus' then
            return true
        end
        if name == 'PxeOption' then
            local ok = self:set_pxe_option(value)
            local l_ctx = ctx.get_context()
            if not ok then
                log:operation(l_ctx:get_initiator(), 'general_hardware',
                'Set the Https State of PCIeCard%s(%s) to %s failed',
                self.pciecard['SlotID'], self.pciecard['Name'], dpu_enums.pxe_option_log_table[value])
            else
                log:operation(l_ctx:get_initiator(), 'general_hardware',
                'Set the Https State of PCIeCard%s(%s) to %s succsessfully',
                self.pciecard['SlotID'], self.pciecard['Name'], dpu_enums.pxe_option_log_table[value])
            end
            return ok
        end
        -- 本组件触发的property change信号，sender为nil，只做赋值操作
        if not change_property_map[name] then
            return true
        end
        return change_property_map[name](self, name, value)
    end)
    self.dpucard_system.property_before_change:on(function (name, value, sender)
        if not sender then
            return true
        end
        if not change_property_map[name] then
            return true
        end
        return change_property_map[name](self, name, value)
    end)
end

local update_prop_map = {
    MRCLogLevel = function (obj)
        local mrc = obj:get_mrc_log_level()
        if mrc then
            return mrc.level
        end
    end,
    UEFILogLevel = function (obj)
        local uefi = obj:get_uefi_log_level()
        if uefi then
            return uefi.level
        end
    end
}

function dpu_object:update_prop_retry()
    local times, success
    for name, fun in pairs(update_prop_map) do
        -- 重试次数
        times = 12
        while times > 0 do
            success = self:set_property(name, fun(self))
            if success then
                break
            end
            times = times - 1
            skynet.sleep(100)
        end
    end
end

-- 命令下发成功，不一定代表一定会生效，所以需要常驻线程更新状态
-- 资源树上的属性需要和硬件实际状态保持一致
function dpu_object:update_property_task()
    skynet.fork(function ()
        while true do
            skynet.sleep(1000)
            self:update_prop_retry()
        end
    end)
end

-- 当值变化时，更新资源树属性
function dpu_object:set_property(name, value)
    if not self.inner[name] then
        log:debug("the property %s does not exist", name)
        return false
    end
    -- 资源树属性值可以为false，但不可能为nil
    if value == nil then
        log:debug("the property %s cannot be set to nil", name)
        return false
    end
    -- 类型不一致也不能设置
    if type(self.inner[name]) ~= type(value) then
        log:debug("the property %s cannot be set to %s", name, type(value))
        return false
    end
    if self.inner[name] ~= value then
        self.inner[name] = value
    end
    return true
end

function dpu_object:set_bios_log_prop(type, level)
    if type == BIOS_LOG_TYPE.MRC and self.inner.MRCLogLevel ~= level then
        self.inner.MRCLogLevel = level
    elseif type == BIOS_LOG_TYPE.UEFI and self.inner.UEFILogLevel ~= level then
        self.inner.UEFILogLevel = level
    end
end

function dpu_object:set_bios_log_level(type, level)
    local ok, result = pcall(function()
        return self.std_smbus:SetBiosLogLevel(string.char(type) .. string.char(level))
    end)
    if not ok then
        log:error('set dpu card bios log level failed, result : %s', result)
        return
    end
    return ok
end

function dpu_object:get_mrc_log_level()
    local ok, data = pcall(function ()
        return self.std_smbus:GetBiosLogLevel('\x01')
    end)
    if not ok then
        log:debug('get dpu card mrc log level failed, result : %s', data)
        return
    end
    return data
end

function dpu_object:get_uefi_log_level()
    local ok, data = pcall(function ()
        return self.std_smbus:GetBiosLogLevel('\x02')
    end)
    if not ok then
        log:debug('get dpu card uefi log level failed, result : %s', data)
        return
    end
    return data
end

function dpu_object:set_sol_switch(sol_switch_type)
    local ok, _ = pcall(function()
        return self.std_smbus:SetSolSwitch(table.concat({
            '\x01', '\x01',
            string.char(SOL_SWITCH_INDEX), string.char(sol_switch_type)
        }))
    end)
    return ok
end

function dpu_object:get_sol_switch()
    local ok, data =  pcall(function()
        return self.std_smbus:GetSolSwitch()
    end)
    if not ok then
        log:debug('get dpu card sol switch failed, result : %s', data)
        return
    end
    return data
end

function dpu_object:update_pciecard_nodeid()
    self.pciecard['NodeID'] = 'PCIeCard' .. self.pciecard['SlotID']
end

-- 获取SerialManagement对象，需要等对象分发完
function dpu_object.get_serial_mgmt_objects(bus)
    local rsp
    local cnt = 0
    local last_cnt = 0
    local ok
    while true do
        skynet.sleep(3000) -- 等待30s
        ok, rsp = pcall(mdb.get_sub_objects, bus, SERIAL_MGMT_PATH, SERIAL_MGMT_INTF)
        if not ok then
            log:error('[DPU] get SerialManagement object list fail, error: %s', rsp)
        end
        cnt = 0
        rsp:fold(function(obj)
            cnt = cnt + 1
        end)
        if cnt ~= 0 and cnt == last_cnt then
            break
        end
        last_cnt = cnt
    end
    if log:getLevel() >= log.INFO then
        log:info('[DPU] find %s SerialManagement objects', cnt)
    end
    return rsp
end

-- 获取与串口录音有关的连接
function dpu_object:get_serial_record_connections(bus)
    if log:getLevel() >= log.INFO then
        log:info('[DPU Service] start serial connection')
    end
    local serial_mgmt_objs = dpu_object.get_serial_mgmt_objects(bus)
    if not serial_mgmt_objs then
        log:error('[DPU] get_serial_mgmt_objects fail')
        return
    end
    -- 遍历资源树上的SerialManagement对象，跟dpu对象的SerialRecordConnect数组比较
    serial_mgmt_objs:fold(function(obj)
        for i = 1, #self.inner.SerialRecordConnect do
            -- 如果SerialManagement对象与dpu对象的SerialRecordConnect数组有完全匹配的，说明是要设置的connection
            if obj.SrcSerial == self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.SrcSerial] and obj.DestSerial ==
                self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.DestSerial] then
                log:info('[DPU] matched connection, Source: %s, Destination: %s', obj.Source,
                    obj.Destination)
                self.exact_conn[obj.Id] = obj
            -- 如果有任意一个src或dest匹配的，说明是与将要设置的connection互斥
            elseif obj.SrcSerial == self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.SrcSerial] or
                obj.SrcSerial == self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.DestSerial] or
                obj.DestSerial == self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.SrcSerial] or
                obj.DestSerial == self.inner.SerialRecordConnect[i][SERIAL_RECORD_CONNECT.DestSerial] then
                log:info('[DPU] mutually exclusive connection, Source: %s, Destination: %s',
                    obj.Source, obj.Destination)
                self.mutex_conn[obj.Id] = obj
            end
        end
    end)
    return
end

-- 建立串口录音连接
function dpu_object:establish_serial_record_connection(bus)
    self:get_serial_record_connections(bus)
    -- 检查互斥连接，如果与录音功能互斥的connection已建立，则不建立录音功能的connection
    for _, serial_mgmt_obj in pairs(self.mutex_conn) do
        if serial_mgmt_obj.Availability == 1 and serial_mgmt_obj.CurrentConnectStatus == 1 then
            log:warn('[DPU] mutually exclusive connection: %s', serial_mgmt_obj.path)
            return
        end
    end
    for _, serial_mgmt_obj in pairs(self.exact_conn) do
        if serial_mgmt_obj.Availability == 1 and serial_mgmt_obj.CurrentConnectStatus == 0 then
            local ok, err = pcall(function()
                -- 开启sdi串口录音不用持久化连接方向
                serial_mgmt_obj:SetConnectStatus(context.new(), false)
            end)
            if ok then
                log:info('[DPU] set %s connect status success', serial_mgmt_obj.path)
            else
                log:error('[DPU] set %s connect status fail, error: %s', serial_mgmt_obj.path, err)
            end
        end
    end
end

function dpu_object:get_pcb_id()
    skynet.fork(function ()
        local ok, value
        local retries = 0
        local flag = false
        repeat
            ok, value = pcall(function()
                -- 偏移为0x31
                return self.ref_id_chip:Read(context.get_context_or_default(), 0x31, 1)
            end)
            if not ok and flag == false then
                flag = true
                log:error('[DPU] read pcb id fail, error: %s', value)
            end
            retries = retries + 1
            skynet.sleep(100) -- 每秒更新一次，最多重试60s
        until ok and value and value:byte() > 0 or retries > 60

        local pcb_id = value:byte()
        log:notice('[DPU] get pcbid:%s', pcb_id)
        -- PcbID的合法取值为1-26
        if pcb_id < 1 or pcb_id > 26 then
            log:error('[DPU] invalid pcb id: %s', pcb_id)
            return
        end
        -- ASCII码偏移64转成A/B/C
        self.pciecard['PcbVersion'] = '.' .. string.char(pcb_id + 64)
        self.dpucard_inventory['PCBVersion'] = self.pciecard['PcbVersion']
    end)
end

function dpu_object:get_board_id()
    local board_id = self.pciecard['BoardID']
    return board_id
end

-- 获取扩展卡是否在位，通过board id判断
function dpu_object:get_extend_card_presence()
    if self:is_dpu_card() or self:is_sdi_5_0_card() then
        self:set_presence('NetworkAdapterPresence', 1, 1)
        return
    end
    local ok, value = pcall(function()
        -- 偏移为0x33
        return self.ref_id_chip:Read(context.get_context_or_default(), 0x33, 1)
    end)
    if not ok then
        log:error('[DPU] read extend card board id fail, error: %s', value)
        return
    end

    local board_id = value:byte()
    -- SDI 5.1扩展卡的board id为0xAE
    if board_id == 0xAE then
        self:set_presence('NetworkAdapterPresence', 1, 1)
    else
        self:set_presence('NetworkAdapterPresence', 1, 0)
    end
end

function dpu_object:check_heartbeat_loss()
    local value
    if self:fetch_health() then
        value = 0
    else
        self.heartbeat_debounce = self.heartbeat_debounce + 1
        -- 连续3次读取失败产生告警
        if self.heartbeat_debounce < 3 then
            return
        end
        value = 1
    end

    self.heartbeat_debounce = 0

    self:generate_alarm('HeartBeatLoss', value)
end

function dpu_object:fetch_capability()
    local ok, value = pcall(function()
        return self.std_smbus:GetCapability()
    end)
    if not ok then
        log:error('[DPU] get capability fail, error: %s', value)
        return
    end
    -- 保存在chip
end

function dpu_object:fetch_health()
    -- 只关心返回值
    local ok = pcall(function()
        return self.std_smbus:GetHealth()
    end)
    return ok
end

function dpu_object:calc_power_read_fail_duration()
    local cur_timestamp = vos.vos_tick_get()
    if self.power_read_fail_timestamp == 0 then
        self.power_read_fail_timestamp = cur_timestamp
        return 0
    end
    return os.difftime(cur_timestamp, self.power_read_fail_timestamp)
end

function dpu_object:fetch_power()
    local old_value = self.dpucard_metrics['PowerWatts']
    local new_value
    local ok, value = pcall(function()
        return self.std_smbus:GetPower()
    end)
    if ok then
        self.power_read_fail_timestamp = 0
        if value.power == PCIE_SENSOR_NA_READING or value.power == PCIE_SENSOR_INVALID_READING then
            new_value = SENSOR_NA_READING
        else
            new_value = value.power
        end

        self.fetch_power_failed_switch = true
    else
        log:debug('[DPU] get power fail, error: %s', value)
        local duration = self:calc_power_read_fail_duration()
        if self.fetch_power_failed_switch then
            log:error('[DPU] get power fail, duration: %d, mcu_reset_time: %d, error: %s',
                duration, self.mcu_reset_time, value)
            self.fetch_power_failed_switch = false
        end
        -- 按3倍阈值计算
        if duration > self.mcu_reset_time * 3 then
            if old_value & SENSOR_INVALID_READING == SENSOR_INVALID_READING then
                return
            end
            new_value = SENSOR_INVALID_READING
        end
    end

    local current_power = self.dpucard_metrics['PowerWatts']
    -- 功率变化超过5W上报日志
    local power_change_watt = math.abs(new_value - current_power)
    if new_value ~= SENSOR_INVALID_READING and new_value ~= SENSOR_NA_READING and power_change_watt >= 5 then
        log:notice('[DPU] power change exceeds 5W, from %s to %s', current_power, new_value)
    end
    self.dpucard_metrics['PowerWatts'] = new_value
    if new_value ~= SENSOR_INVALID_READING and new_value ~= SENSOR_NA_READING then
        self.dpucard_metrics['ActualPowerWatts'] = new_value // 10
    else
        self.dpucard_metrics['ActualPowerWatts'] = SENSOR_INVALID_READING
    end
end

function dpu_object:fetch_mcu_fw_ver()
    local ok, value = pcall(function()
        return self.std_smbus:GetMcuFwVer()
    end)
    if not ok then
        log:debug('[DPU] get mcu version fail, error: %s', value)
        return
    end

    self.dpucard['MCUVersion'] = value
    return value
end

function dpu_object:fetch_bios_version()
    local ok, value = pcall(function()
        return self.std_smbus:GetBIOSVersion()
    end)
    if not ok then
        log:debug('[DPU] get bios version fail, error: %s', value)
        return
    end
    if self.dpucard_system['BIOSVersion'] ~= value and value ~= 'NA' then
        log:notice('[DPU] bios version changed, from %s to %s', self.dpucard_system['BIOSVersion'], value)
    end
    self.dpucard_system['BIOSVersion'] = value
    return value
end

function dpu_object:fetch_cpu_arch()
    local ok, value = pcall(function()
        return self.std_smbus:GetCpuArch()
    end)
    if not ok then
        log:debug('[DPU] get cpu_arch version fail, error: %s', value)
        return
    end
    self.dpucard_processor['Architecture'] = value
    return value
end

function dpu_object:fetch_cpu_cores()
    local ok, value = pcall(function()
        return self.std_smbus:GetCpuCores()
    end)
    if not ok then
        log:debug('[DPU] get cpu_cores fail, error: %s', value)
        return
    end
    self.dpucard_processor['TotalCores'] = value
    return value
end

function dpu_object:fetch_disk_size()
    local ok, value = pcall(function()
        return self.std_smbus:GetDiskSize()
    end)
    if not ok then
        log:debug('[DPU] get disk_size fail, error: %s', value)
        return
    end
    self.dpucard_system['DiskCapacityGiB'] = value
    return value
end

function dpu_object:fetch_mem_size()
    local ok, value = pcall(function()
        return self.std_smbus:GetMemSize()
    end)
    if not ok then
        log:debug('[DPU] get mem_size fail, error: %s', value)
        return
    end
    self.dpucard_system['MemorySizeGiB'] = value
    return value
end

function dpu_object:fetch_cpld_fw_ver()
    local cpld_num = self.dpucard.CPLDCount
    local multi_logic_version = {}
    for arg = 1, cpld_num, 1 do
        local ok, value = pcall(function()
            return self.std_smbus:GetCpldFwVer(arg - 1)
        end)
        if not ok then
            log:debug('[DPU] get cpld%d version fail, error: %s', arg, value)
            return
        end
        multi_logic_version[string.format("CPLD%d", arg)] = value
    end

    self.dpucard['LogicVersion'] = multi_logic_version.CPLD1
    self.dpucard['MultiLogicVersion'] = multi_logic_version
    return multi_logic_version
end

function dpu_object:fetch_vrd_fw_ver()
    local ok, value = pcall(function()
        return self.std_smbus:GetVrdFwVer()
    end)
    if not ok then
        log:debug('[DPU] get vrd version fail, error: %s', value)
        return
    end

    -- VRD固件版本一个数字，不分段
    self.dpucard['VrdVersion'] = value
    return value
end

function dpu_object:update_fw_ver()
    -- 正在升级不用更新
    if dpu_enums.DPU_UPGRADING == 1 then
        log:debug('[update mcu fw ver] MCU or VRD is already Upgrading')
        return
    end
    -- 尝试获取并更新sdi mcu版本号
    local version = self:fetch_mcu_fw_ver()
    if self.mcu_fw_obj and version and self.mcu_fw_obj.Version ~= version then
        log:notice('[update mcu fw ver] update MCU version %s', version)
        self.mcu_fw_obj.Version = version
    end

    -- 尝试获取并更新sdi cpld版本号
    local multi_logic_version = self:fetch_cpld_fw_ver()
    local cpld_num = self.dpucard.CPLDCount
    for i = 1, cpld_num, 1 do
        if self.cpld_fw_obj and self.cpld_fw_obj[i] and multi_logic_version
            and self.cpld_fw_obj[i].Version ~= multi_logic_version["CPLD" .. i] then
            log:notice('[update cpld fw ver] update CPLD%s version %s', i, multi_logic_version["CPLD" .. i])
            self.cpld_fw_obj[i].Version = multi_logic_version["CPLD" .. i]
        end
    end

    if not self:is_dpu_card() then
        -- 尝试获取并更新sdi vrd版本号
        version = self:fetch_vrd_fw_ver()
        if self.vrd_fw_obj and version and self.vrd_fw_obj.Version ~= version then
            log:notice('[update vrd fw ver] update VRD version %s', version)
            self.vrd_fw_obj.Version = version
        end
    end

end

function dpu_object:get_cpld_manufacture_id()
    local cpld_num = self.dpucard.CPLDCount
    self.cpld_manufacture_ids = {}
    for arg = 1, cpld_num, 1 do
        local ok, value = pcall(function()
            return self.std_smbus:GetCpldManufacture(arg - 1)
        end)
        if not ok then
            log:error('[DPU] get CPLD%s manufacture fail, error: %s', arg, value)
            return
        end
    
        if not value or not value.manufacture_id then
            log:error('[DPU] get CPLD%d manufacture fail', arg)
            return
        end

        table.insert(self.cpld_manufacture_ids, value.manufacture_id)
    end

    -- 将cpld固件中的-占位符替换为厂商ID
    for k, v in pairs(self.cpld_fw_raw) do
        local manufacture_id = self.cpld_manufacture_ids[utils.check_CPLD_upgrade_name(v)]
        if manufacture_id then
            self.cpld_fw_raw[k] = string.gsub(v, "-", string.format('%02x', manufacture_id))
        end
    end
end

function dpu_object:get_mcu_manufacture_id()
    local ok, value = pcall(function()
        return self.std_smbus:GetMcuManufacture()
    end)
    if not ok then
        log:error('[DPU] get manufacture fail, error: %s', value)
        return
    end

    self.mcu_manufacture_id = value
    -- 将mcu固件中的-占位符替换为厂商ID
    for k, v in pairs(self.mcu_fw_raw) do
        self.mcu_fw_raw[k] = string.gsub(v, "-", string.format('%02x', value))
    end
end

function dpu_object:register_cpld_firmware_info(bus)
    local cpld_num = self.dpucard.CPLDCount
    local cpld_fw_id, version, software_id
    local board_name = ''
    self.cpld_fw_obj = {}

    skynet.fork(function()
        for i = 1, cpld_num, 1 do
            cpld_fw_id = cpld_num == 1 and self.cpld_fw_id or
                (i == 1 and self.cpld1_fw_id or self.cpld2_fw_id)
            version = self.dpucard.MultiLogicVersion[i]
            if cpld_num > 1 then
                board_name = ' ' .. (i == 2 and self.inner.CPUBoardName or self.inner.NICBoardName)
            end
            software_id = 'CPLD-' .. self.pciecard['BoardName'] .. '-' .. i
            log:notice('[DPU] cpld register firmware: Id %s, version: %s', cpld_fw_id, version)

            local params = {
                Id = cpld_fw_id,
                Name = 'PCIe'.. self:get_slot_id() .. board_name .. ' CPLD',
                Version = version,
                BuildNum = '',
                ReleaseDate = '',
                LowestSupportedVersion = '',
                SoftwareId = software_id,
                Manufacturer = 'Huawei',
                Location = '',
                State = 'Enabled',
                Severity = 'Informational'
            }
    
            local ok, rsp = utils.retry_func(100, 6, client.PFirmwareInventoryFirmwareInventoryAdd,
                client, ctx.new(), params, true, 1, 10)     -- DPU CPLD的升级包需要tmp目录10MB空间
            if not ok then
                log:error('[DPU] fail to register firmware %s error message: %s', params.Id, rsp)
            else
                self.cpld_fw_obj[i] = mdb.get_object(bus, utils.FWINVENTORY_PATH .. params.Id, utils.FIRMWARE_INFO_INTERFACE)
                self.registered_fw_id = params.Id -- Firmware inventory object path suffix on d-bus
                log:notice('[DPU] register DPU CPLD(%s) to firmware inventory', params.Id)
            end
        end
    end)
end

function dpu_object:register_mcu_firmware_info(bus)
    log:notice('[DPU] register firmware: Id %s', self.mcu_fw_id)
    skynet.fork(function()
        local params = {
            Id = self.mcu_fw_id,
            Name = 'PCIe'.. self:get_slot_id() .. ' MCU',
            Version = self.dpucard['MCUVersion'],
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = 'MCU-' .. self.pciecard['BoardName'],
            Manufacturer = 'Huawei',
            Location = '',
            State = 'Enabled',
            Severity = 'Informational'
        }
        local ok, rsp = utils.retry_func(100, 6, client.PFirmwareInventoryFirmwareInventoryAdd,
            client, ctx.new(), params, true, 1, 10)     -- DPU MCU的升级包需要tmp目录10MB空间
        if not ok then
            log:error('[DPU] fail to register firmware %s error message: %s', params.Id, rsp)
        else
            self.mcu_fw_obj = mdb.get_object(bus, utils.FWINVENTORY_PATH .. params.Id, utils.FIRMWARE_INFO_INTERFACE)
            self.registered_fw_id = params.Id -- Firmware inventory object path suffix on d-bus
            log:notice('[DPU] register DPU MCU(%s) to firmware inventory', params.Id)
        end
    end)
end

function dpu_object:register_vrd_firmware_info(bus)
    log:notice('[DPU] register firmware: Id %s', self.vrd_fw_id)
    skynet.fork(function()
        local params = {
            Id = self.vrd_fw_id,
            Name = 'PCIe'.. self:get_slot_id() .. ' VRD',
            Version = self.dpucard['VrdVersion'],
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = 'VRD-' .. self.pciecard['BoardName'],
            Manufacturer = 'Huawei',
            Location = '',
            State = 'Enabled',
            Severity = 'Informational'
        }
        local ok, rsp = utils.retry_func(100, 6, client.PFirmwareInventoryFirmwareInventoryAdd,
            client, ctx.new(), params, true, 1, 10)     -- DPU MCU的升级包需要tmp目录10MB空间
        if not ok then
            log:error('[DPU] fail to register firmware %s error message: %s', params.Id, rsp)
        else
            self.vrd_fw_obj = mdb.get_object(bus, utils.FWINVENTORY_PATH .. params.Id, utils.FIRMWARE_INFO_INTERFACE)
            self.registered_fw_id = params.Id -- Firmware inventory object path suffix on d-bus
            log:notice('[DPU] register DPU VRD(%s) to firmware inventory', params.Id)
        end
    end)
end

function dpu_object:get_slot_id()
    local retries = 0
    local slot_id
    repeat
        retries = retries + 1
        slot_id = self.pciecard['SlotID']
        if not slot_id or slot_id == 0 then
            skynet.sleep(200)
        end
    until slot_id and slot_id ~= 0 or retries > 30 -- 防止出现slot_id没更新的情况
    return slot_id
end

function dpu_object:query_upgrade_status()
    local ok, payload = pcall(function()
        return self.std_smbus:GetMcuUpgradeStatus()
    end)
    if not ok then
        return
    end
    return payload.status
end

function dpu_object:fetch_mcu_reset_time()
    local ok, value = pcall(function()
        return self.std_smbus:GetMcuResetTime()
    end)
    if not ok then
        log:debug('[DPU] get mcu reset time fail, error: %s', value)
        return
    end

    self.mcu_reset_time = value
end

function dpu_object:get_dpu_boot_option()
    return pcall(function()
        return self.std_smbus:GetBootOption()
    end)
end

function dpu_object:fetch_boot_option()
    local ok, value = self:get_dpu_boot_option()
    if not ok then
        log:debug('[DPU] get boot option fail, error: %s', value)
        return
    end

    -- 校验数据是否符合预期
    if value.type1 ~= 1 or value.type2 ~= 2 then
        return
    end

    if self.dpucard['BootSourceOverrideMode'] ~= value.boot_option then
        log:notice('[DPU] boot option override mode changed, from %s to %s',
            self.dpucard['BootSourceOverrideMode'], value.boot_option)
    end
    if self.dpucard['BootSourceOverrideEnabled'] ~= value.save_option then
        log:notice('[DPU] boot option override enable changed, from %s to %s',
            self.dpucard['BootSourceOverrideEnabled'], value.save_option)
    end
    self.dpucard['BootSourceOverrideMode'] = value.boot_option
    self.dpucard['BootSourceOverrideEnabled'] = value.save_option
end

function dpu_object:get_dpu_pxe_option()
    local ok, value = pcall(function()
        return self.std_smbus:GetPxeOption()
    end)

    -- 若pxe option为非法值，则为默认值0
    if ok and not self:get_pxe_option_str(value.pxe_option) then
        value.pxe_option = 0
    end

    return ok, value
end

function dpu_object:set_dpu_pxe_option(pxe_option)
    return pcall(function()
        return self.std_smbus:SetPxeOption(pxe_option)
    end)
end

function dpu_object:get_dpu_secure_boot()
    local ok, value = pcall (function ()
        return self.std_smbus:GetSecureBoot()
    end)

    return ok, value
end

function dpu_object:set_dpu_secure_boot(secure_boot)
    return pcall(function ()
        return self.std_smbus:SetSecureBoot(secure_boot)
    end)
end

function dpu_object:get_dpu_LLDP_status()
    local cpld_num = self.dpucard.CPLDCount
    if cpld_num == 1 then
        return false, "single cpld dpu not support this operation"
    end
    local ok, value = pcall(function ()
        return self.std_smbus:GetLLDPStatus()
    end)
    return ok, value
end

function dpu_object:set_dpu_LLDP_status(status)
    local cpld_num = self.dpucard.CPLDCount
    if cpld_num == 1 then
        return false, "single cpld dpu not support this operation"
    end
    return pcall(function ()
        return self.std_smbus:SetLLDPStatus(status)
    end)
end

function dpu_object:get_dpu_uuid()
    return pcall(function()
        return self.std_smbus:GetUUID()
    end)
end

function dpu_object:fetch_pxe_option()
    --mcu调用5次失败，就不再更新pxe option
    if self.fetch_pxe_option_task then
        return
    end

    local ok, value = utils.retry_func(100, 5, function()
        return self:get_dpu_pxe_option()
    end)
    if not ok then
        log:error('[DPU] Cannot get pxe option, error: %s', value)
        self.fetch_pxe_option_task = true
        return
    end

    -- 4表示组合pxe功能选项开关
    if value.type ~= 4 then
        return
    end
    local pxe_option = self:get_pxe_option_str(value.pxe_option)
    if self.dpucard['PxeOption'] ~= pxe_option then
        log:notice('[DPU] pxe option changed, from %s to %s', self.dpucard['PxeOption'], pxe_option)
    end
    self.dpucard['PxeOption'] = pxe_option
end

function dpu_object:set_pxe_option(pxe_opt)
    if not self:get_pxe_option(pxe_opt) then
        log:error('[DPU] invalid pxe option: %s', pxe_opt)
        return false
    end
    local pxe_opt_digit = self:get_pxe_option(pxe_opt)

    local ok, value = self:set_dpu_pxe_option(pxe_opt_digit)

    if not ok then
        log:error('[DPU] set dpu pxe option fail,pxe_opt_digit: %s, error: %s', pxe_opt_digit, value)
        return false
    end

    return ok
end

function dpu_object:fetch_secure_boot()
    if not self:is_dpu_card() then
        if not self.fetch_secure_boot_switch then
            return
        end
        log:error('[DPU] Only Dpucard support operate secure boot')
        self.fetch_secure_boot_switch = false
        return
    end
    
    local ok, value = self:get_dpu_secure_boot()
    if not ok then
        if not self.fetch_secure_boot_switch then
            return
        end
        log:error('[DPU] Cannot get secure boot from MCU, error: %s', value)
        self.fetch_secure_boot_switch = false
        return
    end

    self.fetch_secure_boot_switch = true

    --2表示安全启动项开关
    if value.type ~= 2 then
        return
    end

    local secure_boot_option = value.secure_boot == 1 and true or false
    if self.dpucard_system['SecureBootOptionEnabled'] ~= secure_boot_option then
        log:notice('[DPU] secure boot option changed, from %s to %s',
            self.dpucard_system['SecureBootOptionEnabled'], secure_boot_option)
    end
    self.dpucard_system['SecureBootOptionEnabled'] = secure_boot_option
end

function dpu_object:set_secure_boot(secure_boot)
    local secure_boot_value = secure_boot and 1 or 0
    local ok, value = self:set_dpu_secure_boot(secure_boot_value)
    if not ok then
        log:error('[DPU] set dpu secure boot failed, secure_boot: %s, error: %s', secure_boot, value)
        return false
    end

    return ok
end

function dpu_object:fetch_LLDP_status()
    local ok, value = self:get_dpu_LLDP_status()
    if not ok then
        if not self.fetch_LLDP_status_switch then 
            return
        end
        log:error("[DPU] Cannot get LLDP status from MCU, error: %s", value)
        self.fetch_LLDP_status_switch = false
        return
    end

    self.fetch_LLDP_status_switch = true

    -- 5表示LLDP使能开关
    if value.type ~= 5 then
        return
    end

    self.LLDP_status = value.status
end

function dpu_object:fetch_dpu_uuid()
    local ok, uuid = self:get_dpu_uuid()
    if not ok then
        log:error('[DPU] get uuid fail, error: %s', uuid)
        return
    end

    -- 0表示UUID未上报
    if uuid == 0 then
        log:debug('[DPU] not report uuid')
        return
    end

    self.dpucard['UUID'] = uuid
end

function dpu_object:set_presence(prop, bit, presence)
    local old_value = self.dpucard[prop]
    if presence == 1 then
        self.dpucard[prop] = old_value | bit
    elseif presence == 0 then
        self.dpucard[prop] = old_value & ~bit
    end
end

function dpu_object:fetch_m2_presence()
    local ok, value = pcall(function()
        return self.std_smbus:GetPresence()
    end)
    if not ok then
        log:debug('[DPU] get M.2 disk presence status fail, error: %s', value)
        return
    end

    for i = 1, #value - 1, 2 do
        if value[i] == DEV_PRESENCE_ID.SSD1 then
            self:set_presence('M2SlotPresence', 1, value[i + 1])
        elseif value[i] == DEV_PRESENCE_ID.SSD2 then
            self:set_presence('M2SlotPresence', 2, value[i + 1])
        end
    end
end

-- set_sys_time 设置系统时间
function dpu_object:set_sys_time(sys_time)
    local ok, _ = pcall(function()
        return self.std_smbus:SetSysTime(table.concat({
            string.char(sys_time.sec), string.char(sys_time.min), string.char(sys_time.hour),
            string.char(sys_time.day), string.char(sys_time.month),
            string.char(sys_time.year - 2000), string.char(sys_time.wday - 1)
        }))
    end)
    return ok
end

function dpu_object:fetch_mac_address()
    local mac_info = {}
    local request_num = self:get_board_id() == dpu_enums.BOARD_ID_FOR_SP923H
        and dpu_enums.REQUEST_NUM_FOR_SP923H or dpu_enums.REQUEST_NUM_FOR_COMMON
    for arg = 1, request_num do
        local ok, data = pcall(function()
            return self.std_smbus:GetMacAddress(arg - 1)
        end)
        if not ok then
            return false
        end
        for _, mac in ipairs(data) do
            table.insert(mac_info, mac)
        end
    end

    for i, data in ipairs(mac_info) do
        if self.dpucard['PfMacInfo'] == nil or self.dpucard['PfMacInfo'][i] == nil then
            log:notice('[DPU] mac address changed, from nil to %s', table.concat(data, ','))
        elseif table.concat(self.dpucard['PfMacInfo'][i], ',') ~= table.concat(data, ',') then
            log:notice('[DPU] mac address changed, from %s to %s',
                table.concat(self.dpucard['PfMacInfo'][i], ','), table.concat(data, ','))
        end
    end
    self.dpucard['PfMacInfo'] = mac_info
    return true
end

function dpu_object:get_dpu_ip()
    return pcall(function()
        return self.std_smbus:GetSdiIp(string.char(CONFIG_OS))
    end)
end

function dpu_object:fetch_sdi_ip()
    local ok, data = self:get_dpu_ip()
    if ok then
        local ipv4 = string.format('%d.%d.%d.%d', string.unpack('BBBB', string.pack('I', data.ipv4)))
        if self.dpucard['StorageIpAddr'] ~= ipv4 then
            log:notice('[DPU] sdi ip changed, from %s to %s', self.dpucard['StorageIpAddr'], ipv4)
        end
        if self.dpucard['StorageIpVlan'] ~= data.vlan then
            log:notice('[DPU] sdi vlan changed, from %s to %s', self.dpucard['StorageIpVlan'], data.vlan)
        end
        self.dpucard['StorageIpAddr'] = ipv4
        self.dpucard['StorageIpVlan'] = data.vlan
    end
    return ok
end

function dpu_object:set_sdi_ipv4(ip_type, ipv4, mask, vlan)
    local ok, err = pcall(function()
        return self.std_smbus:SetSdiIpv4(table.concat({
            string.char(ip_type), dal_addr_pack(ipv4), dal_addr_pack(mask), string.pack('H', vlan)
        }))
    end)
    if not ok then
        log:error('set sdi ipv4 failed, err = %s', err)
    end
    return ok
end

function dpu_object:set_sdi_ipv6(ip_type, ipv6, prefix_length, vlan)
    local ok, _ = pcall(function()
        return self.std_smbus:SetSdiIpv6(table.concat({
            string.char(ip_type), dal_addr_ipv6_pack(ipv6), string.char(prefix_length), string.pack('H', vlan)
        }))
    end)
    return ok
end

function dpu_object:set_sdi_slot(slot_id)
    local ok, _ = pcall(function()
        return self.std_smbus:SetSdiSlot(string.char(slot_id))
    end)
    return ok
end

function dpu_object:set_sdi_host_os_status(data)
    log:notice('send host os status to sdi mcu')
    return pcall(function()
        return self.std_smbus:SetHostOsStatus(string.char(data))
    end)
end

function dpu_object:generate_alarm(type, value)
    if value ~= self.dpucard_fault[type] then
        self.dpucard_fault[type] = value
    end
end

function dpu_object:set_frudata(ref_frudata, elabel)
    local prop = {}
    local value = {}
    for elabel_prop, val in pairs(elabel) do
        table.insert(prop, elabel_prop)
        table.insert(value, val)
    end
    local ok, rsp = utils.retry_func(300, 100, function()
        return pcall(ref_frudata.Update, ref_frudata, context.new(), prop, value)
    end)
    if not ok then
        log:error('[DPU] set fru data fail, error: %s', rsp)
    end
end

function dpu_object:get_sdi_elabel(callback)
    local ok, elabel = pcall(function()
        return self.std_smbus:GetElabel(callback)
    end)
    if ok then
        log:notice('[DPU]set fru data successfully.')
        return elabel
    end
    log:error('[DPU]get fru data from MCU failed, error: %s', elabel)
    return nil
end

function dpu_object:set_dpu_boot_option(type, value)
    return pcall(function()
        return self.std_smbus:SetBootOption('\x01' .. string.char(type) .. '\x02' ..
                                                string.char(value))
    end)
end

function dpu_object:reset_sdi_card()
    return pcall(function()
        if self.inner.PowerState == 'Off' then
            error('dpu card is powered off, cannot reset')
        end
        return self.std_smbus:ResetSystem()
    end)
end

function dpu_object:set_dpu_nmi()
    return pcall(function()
        return self.std_smbus:SetNMI()
    end)
end

function dpu_object:set_dpu_power_state(data)
    return pcall(function()
        return self.std_smbus:SetSystemAC(string.char(data))
    end)
end

function dpu_object:set_dpu_reset_linkage(data)
    return pcall(function()
        return self.std_smbus:SetResetLinkage(string.char(data))
    end)
end

function dpu_object:get_reset_linkage()
    return pcall(function()
        return self.std_smbus:GetResetLinkage()
    end)
end

function dpu_object:get_dpu_status(data)
    return pcall(function()
        return self.std_smbus:GetSystemStatus(string.char(data))
    end)
end

function dpu_object:get_mpu_busy_status()
    return pcall(function()
        return self.std_smbus:GetMPUBusyStatus()
    end)
end

function dpu_object:fetch_power_status()
    local ok, data = self:get_dpu_status(TLV_POWER_STATUS)
    if not ok then
        log:debug('[DPU] get power status failed, error: %s', data)
        return
    end
    
    ok = self:set_property('PowerState', POWER_STATE_TO_STR[data.status])
    if not ok then
        log:error('[DPU] set power status property failed')
        return
    end
end

function dpu_object:fetch_os_status()
    local ok, data = self:get_dpu_status(TLV_OS_STATUS)
    if not ok then
        log:debug('[DPU] get os status fail, error: %s', data)
        return
    end

    self.dpucard['SystemLoadedStatus'] = data.status
end

function dpu_object:fetch_bios_status()
    local board_id = self:get_board_id()
    if board_id ~= dpu_enums.BOARD_ID_FOR_SP923H then
        log:debug('[DPU] board_id %s not support get bios status', board_id)
        return
    end
    local ok, data = self:get_dpu_status(TLV_BIOS_STATUS)
    if not ok then
        log:debug('[DPU] get bios status fail, error: %s', data)
        return
    end
    self.dpucard_system['BiosBootStage'] = data.status
end

function dpu_object:fetch_mpu_busy_status()
    local ok, data = self:get_mpu_busy_status()
    if not ok then
        log:debug('[DPU] get mpu busys status fail, error: %s', data)
        return
    end
    -- 因为SDI返回0代表繁忙, 1代表正常, 而资源树属性MPUBusyStatus定义为0：空闲 1：繁忙，故需转化
    local mpu_busy_status = data == 1 and 0 or 1
    if self.inner.MPUBusyStatus ~= mpu_busy_status then
        log:notice('[DPU] mpu busys status changed, from %s to %s', self.inner.MPUBusyStatus, mpu_busy_status)
    end
    self.inner.MPUBusyStatus = mpu_busy_status
end

function dpu_object:get_mcu_error_log_data()
    local ok, data = pcall(function()
        return self.std_smbus:GetErrorLog()
    end)
    if not ok then
        log:error('failed to get mcu log, error: %s', data)
        return nil
    end
    return data
end

local function statistic_file_size(file_name)
    if not file_name or not utils_core.is_file(file_name) then
        return false
    end
    local file_stat = utils_core.stat_s(file_name)
    return file_stat.st_size
end

-- 打开文件，需要外部调用函数自行关闭
local function open_log_file(file_name, log_buf_size)
    local MCU_MAX_LOG_SIZE<const> = 1 * 1024 * 1024 -- 从mcu收集日志，日志文件允许最大值1MB
    local file_size = statistic_file_size(file_name)
    if not file_size then
        return file_sec.open_s(file_name, 'wb+')
    end
    if file_size + log_buf_size > MCU_MAX_LOG_SIZE then
        -- 从文件开头开始写, 覆盖原有位置的内容
        return file_sec.open_s(file_name, 'rb+')
    end
    -- 从文件末尾追加写
    return file_sec.open_s(file_name, 'ab+')
end

function dpu_object:get_log_update_status()
    local ok, resp = pcall(function()
        return self.std_smbus:GetLogUpdateStatus()
    end)
    if not ok then
        log:debug('[DPU] get log update status failed, error: %s', resp)
        return
    end
    return resp
end

function dpu_object:collect_mcu_error_log()
    log_collector.clear_invaild_log()
    local log_dir = log_collector.get_log_dir(self.pciecard)
    if not log_dir then
        return
    end
    if not log_collector.create_dir(log_dir) then
        log:error('Create pciecard log dir failed.')
        return
    end
    local mcu_log_data = self:get_mcu_error_log_data()
    if not mcu_log_data then
        return
    end
    local log_name = string.gsub('error_log_' .. self.pciecard['Description'] .. '.bin', ' ', '')
    local f_log = open_log_file(log_dir .. log_name, #mcu_log_data)
    if not f_log then
        log:error('[DPU]open %s failed', log_name)
        return
    end
    mc_utils.close(f_log, pcall(f_log.write, f_log, mcu_log_data))
    utils_core.chmod_s(log_dir .. log_name, mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)

    log:notice('collect dpu_card error log finished.')
end

-- 等待日志收集完成再返回
function dpu_object:wait_collect_log()
    local wait_time = LOG_DUMP_MAX_TIME
    while self.collect_status == LOG_DUMP_BUSY and wait_time > 0 do
        skynet.sleep(1000) --间隔10秒判断一次
        wait_time = wait_time - 10 
        log:notice('[DPU] wait_collect_log, please wait')
    end
end

function dpu_object:collect_log_from_mcu()
    if self.collect_status == LOG_DUMP_BUSY then
        log:notice('[DPU] bmc is collecting log from mcu, please wait')
        self:wait_collect_log()
        return
    end
    log:notice('start to collect sdi mcu error log')
    self.collect_status = LOG_DUMP_BUSY
    local ok, err = pcall(function ()
        self:collect_mcu_error_log()
    end)
    if not ok then
        log:error('[DPU] collect log failed, error:%s', err)
    end
    self.collect_status = LOG_DUMP_IDLE
    log:notice('finish to collect sdi mcu error log')
end

function dpu_object:update_mcu_log()
    -- 正在升级不用更新
    if dpu_enums.DPU_UPGRADING == 1 then
        log:debug('[DPU] MCU or VRD is Upgrading, can not collect mcu log')
        return
    end
    if self:get_log_update_status() == 1 then
        self:collect_log_from_mcu()
    end
end

function dpu_object:get_error_code()
    local ok, resp = pcall(function()
        return self.std_smbus:GetErrorCode()
    end)
    if not ok then
        log:debug('[DPU] get error code failed, error: %s', resp)
        return
    end
    return resp
end

function dpu_object:recode_device_temperature_change(property, current_temp)
    -- 温度变化超过5度上报日志
    if math.abs(current_temp - self.dpucard_metrics[property]) >= 5 then
        log:notice('[DPU] device temperature exceeds 5 celsiue, property %s temperature from %s to %s',
            property, self.dpucard_metrics[property], current_temp)
    end
end

function dpu_object:fetch_device_temperature()
    local ok, resp = pcall(function()
        return self.std_smbus:GetDeviceTemperature()
    end)
    if not ok then
        log:debug('[DPU] get device temperature failed, error: %s', resp)
        for prop, _ in pairs(prop_map) do
            self.dpucard_metrics[prop] = PCIE_SENSOR_INVALID_READING
        end
        return
    end
    for prop, device_prop in pairs(prop_map) do
        if resp[device_prop] then
            self:recode_device_temperature_change(prop, resp[device_prop])
            self.dpucard_metrics[prop] = resp[device_prop]
        end
    end
end

function dpu_object:check_component_dpucard(component_name)
    local objects = client:GetComponentObjects()
    for _, v in pairs(objects) do
        if v.Name == component_name then
            self.component_dpucard = v
            return true
        end
    end
    return false
end

-- 获取当前event仓的告警码
function dpu_object:update_latest_alarm_list(bus)
    local ok, alarm_list = event.get_latest_alarm_list(bus)
    if ok then
        log:debug('get_latest_alarm_list successfully. name:%s', self.pciecard['Name'])
    else
        log:debug('get_latest_alarm_list failed. name:%s err:%s', self.pciecard['Name'], alarm_list)
        return
    end

    local event_err_code_list = {}
    -- 通过 ComponentName 和 MessageArgs 里的 err_code 确定当前的告警码。
    for _, alarm in pairs(alarm_list) do
        if not alarm.ComponentName or alarm.ComponentName ~= self.pciecard['DeviceName'] then
            log:debug('alarm.ComponentName not match') -- 不是当前对象的告警
            goto continue
        end
        if not alarm.MessageArgs or alarm.MessageArgs == "" then
            log:debug('alarm.MessageArgs empty')
            goto continue
        end
        local is_ok, alarm_msg_args = pcall(cjson.decode, alarm.MessageArgs)
        if not is_ok then
            log:debug('alarm.MessageArgs decode failed, err: %s', alarm_msg_args)
            goto continue
        end
        -- alarm_msg_args 最后一个元素是 err_code_str
        local lines = mc_utils.split(alarm_msg_args[#alarm_msg_args], ' ')
        if type(lines) ~= "table" or #lines == 0 then
            goto continue
        end
        -- 按空格分隔后最后一段是 err_code
        local err_code = tonumber(lines[#lines])
        -- 更新记录的故障码 和event仓记录的保持一致。
        event_err_code_list[err_code] = err_code
        ::continue::
    end
    self.mcu_err_list = event_err_code_list
end

-- 带外收集智能网卡异常告警 参考v2里面间隔2秒
function dpu_object:update_error_code_by_std_smbus(bus)
    if dpu_enums.DPU_UPGRADING == 1 then
        return
    end

    local component_exist = self:check_component_dpucard(self.pciecard['DeviceName'])
    if not component_exist then
        log:debug('check_component_dpucard fail, return')
        return
    end

    if self.pciecard['SlotID'] == 0 then
        log:debug('check SlotID fail, return')
        return
    end

    local mcu_err_code_list = self:get_error_code()
    if not mcu_err_code_list then
        log:debug('get_error_code fail, return')
        return
    end

    -- 获取event仓记录的当前告警码
    self:update_latest_alarm_list(bus)
    log:debug('update_error_code_by_std_smbus, mcu_err_code_list')

    -- 找出从mcu没有获取，但是有记录的故障码
    for err_code, _ in pairs(self.mcu_err_list) do
        if not mcu_err_code_list[err_code] then
            -- 消除告警
            local ok, resp = pcall(function()
                return self:generate_mcu_event(bus, err_code, 'false')
            end)
            if not ok then
                log:error('deassert error code failed, error: %s', resp)
                break
            end
        end
    end
    -- 找出从mcu获取但是没有记录的故障码
    for err_code, _ in pairs(mcu_err_code_list) do
        if not self.mcu_err_list[err_code] then
            -- 触发告警
            local ok, resp = pcall(function()
                return self:generate_mcu_event(bus, err_code, 'true')
            end)
            if not ok then
                log:error('assert error code failed, error: %s', resp)
                break
            end
        end
    end
end

function dpu_object:get_mcu_event_info(err_code)
    local info_table = dpu_enums.mcu_error_code_desc_event_table
    if not info_table or info_table[err_code] == nil then
        log:debug('Not support error code: %s', tostring(err_code))
        return nil, nil
    end
    local event_key_id = info_table[err_code][1]
    local event_desc_en = info_table[err_code][2]

    return event_key_id, event_desc_en
end

function dpu_object:get_sdi_cpld_info()
    local cpld_num = self.dpucard.CPLDCount
    local cpld_info = ''
    for arg = 1, cpld_num, 1 do
        local ok, resp = pcall(function()
            return self.std_smbus:GetCpldInfo(arg - 1)
        end)
        if not ok then
            log:error('[DPU] CPLD%s get cpld log failed, error: %s', arg, resp)
            return
        end
        cpld_info = cpld_info .. "\n\n" .. resp
    end

    local log_dir = log_collector.get_log_dir(self.pciecard)
    if not log_dir then
        return
    end
    if not log_collector.create_dir(log_dir) then
        log:error('Create pciecard log dir failed.')
        return
    end

    local file_name = log_dir .. 'smartNic_cpld_info' .. self.pciecard['SlotID']

    local fp = file_sec.open_s(file_name, 'w+')
    if not fp then
        log:error('open dpu cpld log file error')
        return
    end

    mc_utils.safe_close_file(fp, function()
        local txt = 'offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F' .. cpld_info
        fp:write(txt)
    end)

    -- 权限控制为640
    utils_core.chmod_s(file_name, mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)
end

local map = {
    [0] = "Absence/Absence",
    [1] = "Presence/Absence",
    [2] = "Absence/Presence",
    [3] = "Presence/Presence"
}

local function get_presence(presence)
    return map[presence & 0x3]
end

function dpu_object:dump_info(file)
    if not file then
        return
    end
    local temp = string.format('%-4u | %-25s | %-25s\n', self:get_slot_id(),
        get_presence(self.dpucard['M2SlotPresence']), get_presence(self.dpucard['NetworkAdapterPresence']))
    file:write(temp)
end

function dpu_object:generate_mcu_event(bus, err_code, state)
    local event_key_id, event_desc_en = self:get_mcu_event_info(err_code)
    if not event_key_id or not event_desc_en then
        log:debug('generate_mcu_event error code failed, error: %s', err_code)
        return
    end
    return add_event.generate_mcu_error_code(bus, err_code, state,
        tostring(self.pciecard['SlotID']), self.pciecard['Description'], event_key_id, event_desc_en,
        self.pciecard['DeviceName'], tostring(PCIE_CARD_SUBJECT_TYPE))
end

-- 获取event仓里当前告警，并更新SDI卡串口线缆在位告警
function dpu_object:update_serial_disconnect_alarm(bus)
    local ok, alarm_list = event.get_latest_alarm_list(bus)
    if ok then
        log:debug('update_serial_disconnect_alarm successfully. name:%s', self.pciecard['Name'])
    else
        log:debug('update_serial_disconnect_alarm failed. name:%s err:%s', self.pciecard['Name'], alarm_list)
        return
    end
    local alarm_exist = false
    -- 根据 ComponentName、EventName 匹配告警
    for _, alarm in pairs(alarm_list) do
        if not alarm.ComponentName or alarm.ComponentName ~= self.pciecard['DeviceName'] or
            alarm.EventName ~= "CableSdiIncorrectConnection" then
            log:debug('alarm.ComponentName not match') -- 不是当前对象的告警
            goto continue
        end
        -- 当前有告警
        alarm_exist = true
        ::continue::
    end
    self.serial_disconnect_alarm = alarm_exist
end

local EVENT_PATH<const> = '/bmc/kepler/Systems/1/Events'
local EVENT_INTERFACE<const> = 'bmc.kepler.Systems.Events'

function dpu_object:general_serial_disconnect_event(bus, smc)
    local component_exist = self:check_component_dpucard(self.pciecard['DeviceName'])
    if not component_exist then
        log:debug('check_component_dpucard fail, return')
        return
    end

    if self.pciecard['SlotID'] == 0 then
        log:debug('check SlotID fail, return')
        return
    end

    local ok, res = pcall(function()
        return smc:Read(context.get_context_or_default(), 0x800c900, 1):byte()
    end)
    if not ok or not res or res > 1 then
        return
    end
    -- 更新当前是否有SDI卡串口线缆不在位告警
    self:update_serial_disconnect_alarm(bus)

    local state
    if res == 1 and self.serial_disconnect_alarm then
        state = 'false'
    elseif res == 0 and not self.serial_disconnect_alarm then
        state = 'true'
    else
        return
    end
    local param = {
        {'ComponentName', self.pciecard['DeviceName']}, {'State', state},
        {'EventKeyId', 'PCIeCard.CableSdiIncorrectConnection'},
        {'MessageArgs', cjson.encode({self:get_dpu_type(), 1})}, {'SystemId', '1'}, {'ManagerId', '1'},
        {'ChassisId', '1'}, {'NodeId', ''}, {'SubjectType', tostring(CABLE_SUBJECT_TYPE)}
    }

    local ok, ret = pcall(event.generate_event, param)
    if ok then
        log:notice(
            'add serial event successfully. name:%s key:PCIeCard.CableSdiIncorrectConnection state:%s',
            self.pciecard['Name'], state)
    else
        log:error(
            'add serial event failed. name:%s key:PCIeCard.CableSdiIncorrectConnection state:%s err:%s',
            self.pciecard['Name'], state, ret)
    end
end

function dpu_object:set_dpu_type(dpu_type)
    self.dpu_type = dpu_type
end

function dpu_object:get_dpu_type()
    return dpu_enums.function_class[self.pciecard['FunctionClass']] or ''
end

function dpu_object:get_pxe_option_str(pxe_option)
    local dpu_type = self:get_dpu_type()
    return dpu_enums.pxe_option_table[pxe_option] or
        (dpu_enums.pxe_option_table[dpu_type] and dpu_enums.pxe_option_table[dpu_type][pxe_option])
end

function dpu_object:get_pxe_option(pxe_option_str)
    local dpu_type = self:get_dpu_type()
    return dpu_enums.pxe_option_table_r[pxe_option_str] or
        (dpu_enums.pxe_option_table_r[dpu_type] and dpu_enums.pxe_option_table_r[dpu_type][pxe_option_str])
end

function dpu_object:is_dpu_card()
    return self.pciecard['FunctionClass'] == FUNCTION_CLASS_FOR_DPU
end

function dpu_object:is_sdi_5_0_card()
    return self.pciecard['BoardID'] == 0xa5
end

function dpu_object:import_public_key(file_path, system_id)
    local ok, res
    for i = 1, 5 do
        ok, res = pcall(function ()
            return self:retry_import_public_key(i, file_path, system_id)
        end)
        if ok and res then
            return true
        end
        log:error('[DPU] import public key failed, retry: %s, err: %s', i, res)
        skynet.sleep(100)
    end
    return false
end

function dpu_object:retry_import_public_key(retry, file_path, system_id)
    log:notice('[DPU] start to import public key failed, retry count: %d', retry)
    local fp<close> = file_sec.open_s(file_path, 'rb')
    if not fp then
        log:error('[DPU] open public key file failed')
        return false
    end
    skynet.sleep(200)
    local key_stream = fp:read('a')

    local ok, import_public_key_success = pcall(function ()
        return self.std_smbus:ImportPublicKey(key_stream, system_id, retry)
    end)
    if not ok or not import_public_key_success then
        log:error('[DPU] send public key to Mcu failed')
        return false
    end

    log:notice('[DPU] import public key success')
    return true
end

return dpu_object
