-- 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: PCIE Group管理
local mdb = require 'mc.mdb'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local cmn = require 'common'
local c_pcie_device = require 'device.class.pcie_device'
local c_pcie_card = require 'device.class.pcie_card'
local msg = require 'pcie_device.types.message'
local file_sec = require 'utils.file'
local utils = require 'mc.utils'
local log_helper = require 'infrastructure.log_helper'
local bs = require 'mc.bitstring'
local client = require 'pcie_device.client'
local skynet = require 'skynet'

local c_device_service = class()

local RET_OK<const> = 0
local RET_ERR<const> = 1
local NULL_VAL<const> = 'N/A'
local BANDWIDTH_REDUCTION<const> = 0
local LINK_SPEED_REDUCED<const> = 1
local PACKAGE_PATH<const> = '/bmc/kepler/Managers/1/Package'

function c_device_service:on_add_object(class_name, mds_obj, position)
    local switch = {
        ['PCIeDevice'] = function()
            local obj = c_pcie_device.new(mds_obj, position, self.bus)
            table.insert(self.pcie_device_list, obj)
        end,
        ['PCIeCard'] = function()
            local obj = c_pcie_card.new(mds_obj, position, self.bus)
            table.insert(self.pcie_card_list, obj)
            self:pcie_oob_mgmt_task()
        end
    }

    if switch[class_name] then
        log:notice('[DeviceService] Add object, class: %s, position: %s', class_name, position)
        switch[class_name]()
        log:notice('[DeviceService] Add object successfully, class: %s, position: %s', class_name, position)
    end
end

local function remove_ele_by_position(list, position)
    for i, obj in pairs(list) do
        if obj.position == position then
            table.remove(list, i)
            break
        end
    end
end

function c_device_service:on_delete_object(class_name, mds_obj, position)
    local switch = {
        ['PCIeDevice'] = function()
            remove_ele_by_position(self.pcie_device_list, position)
        end,
        ['PCIeCard'] = function()
            remove_ele_by_position(self.pcie_card_list, position)
        end
    }

    if switch[class_name] then
        log:notice('[DeviceService] Delete object, class: %s, position: %s', class_name, position)
        switch[class_name]()
    end
end

-- 注册任务，从PcieAddrInfo同步属性值到PCIeDevice完成后需要执行
function c_device_service:register_get_pcie_info_task(pcie_device_obj, position)
    pcie_device_obj.sync_pai_to_device_complete:on(function (device_obj)
        -- 每个position对应唯一一个PCIeCard
        local pcie_card_obj = cmn.find(self.pcie_card_list, function(item)
            if item.position == position then
                return true
            end
        end)
        local ok, err
        if pcie_card_obj then
            cmn.skynet.fork_loop({count = 0}, function ()
                while true do
                    ok, err = pcall(function()
                        -- 1. 获取LinkWidth，LinkWidthAbility
                        pcie_card_obj:get_pcie_lang_info(device_obj)
                        -- 2. 开始同步属性
                        pcie_card_obj:sync_info_to_card(device_obj)
                    end)
                    if not ok then
                        log:debug('[pcie_device] Error=%s', err)
                    end
                    cmn.skynet.sleep(60 * 100)
                end
            end)
        end
    end)
end

function c_device_service:on_add_object_complete(position)
    cmn.skynet.fork(function()
        -- 每个position对应唯一一个PCIeDevice
        local pcie_device_obj = cmn.find(self.pcie_device_list, function(item)
            if item.position == position then
                return true
            end
        end)
        if pcie_device_obj then
            -- 1. 注册任务，从PcieAddrInfo同步属性值到PCIeDevice完成后需要执行
            self:register_get_pcie_info_task(pcie_device_obj, position)
 
            -- 2. 开始同步属性值
            self:sync_pcie_addr_info_to_device(pcie_device_obj)
        end
    end)
end

function c_device_service:contain_raid()
    for _, obj in pairs(self.pcie_card_list) do
        if obj:is_raid() then
            return true
        end
    end
    return false
end

-- PCIe带外管理信息获取任务
function c_device_service:pcie_oob_mgmt_task()
    if self.pcie_oob_mgmt_flag then
        return
    end
    cmn.skynet.fork_loop({count = 0}, function()
        log:info('[DeviceService] PCIe card oob management task.')
        while true do
            for _, obj in pairs(self.pcie_card_list) do
                obj:pcie_oob_mgmt_update_info()
            end
            cmn.skynet.sleep(100 * 2) -- 更新时间间隔2s
        end
    end)
    self.pcie_oob_mgmt_flag = true
end

-- 更新下行连接器的槽位规范版本到PCIeDevice对象的PCIeType
function c_device_service:update_pcie_type(biz_topo_service, obj)
    local pcie_type = biz_topo_service:method_get_pcie_type_by_pos(obj.position)
    obj:set_prop('PCIeType', pcie_type)
end

-- 关联PCIeAddrInfo对象，同步槽位号和BDF信息到PCIeDevice对象
function c_device_service:sync_pcie_addr_info_to_device(obj)
    local pcie_addr_info
    local biz_topo_service = self.service_manager:get_service('biz_topo')
    if not biz_topo_service then
        log:error('[DeviceService] Get sub service biz_topo failed.')
        return
    end
    cmn.skynet.fork(function()
        local retry = 120
        -- 通过BIOS上报加载的PCIe设备（需要更新SlotID），PCIeAddrInfo对象在PCIeDevice的上一级
        while retry > 0 do
            -- devicename已经设置成功的直接跳过
            if obj.device_name_ready then
                log:notice('[DeviceService] device_name_ready, position=%s', obj.position)
                return
            end
            pcie_addr_info = biz_topo_service:method_get_pcie_addr_by_pos(obj.position)
            if pcie_addr_info then
                obj:sync_pcie_addr_info(pcie_addr_info)
                self:update_pcie_type(biz_topo_service, obj)
                return
            end
            retry = retry - 1
            cmn.skynet.sleep(100)
        end
    end)

    cmn.skynet.fork(function()
        -- 其他PCIe设备，静态SlotID，通过DeviceType和SlotID匹配
        local retry = 120
        local pcie_addr_info_list
        while retry > 0 do
            -- 未配置的或者已经设置成功的直接跳过
            if obj:get_prop('DeviceType') == 0 or obj.device_name_ready then
                return
            end
            pcie_addr_info_list = biz_topo_service.biz_topo:get_objs('PCIeAddrInfo')
            for _, addr_obj in pairs(pcie_addr_info_list) do
                if addr_obj:get_prop('ComponentType') == obj:get_prop('DeviceType') and
                    addr_obj:get_prop('SlotID') == obj:get_prop('SlotID') and addr_obj:get_prop('SlotID') ~= 0 then
                    obj:sync_pcie_addr_info(addr_obj)
                    return
                end
            end
            retry = retry - 1
            cmn.skynet.sleep(100)
        end

        log:error(
            '[DeviceService] Can not find matching PCIeAddrinfo, PCIeDevice position=%s DeviceType=%s SlotID=%s',
            obj.position, obj:get_prop('DeviceType'), obj:get_prop('SlotID'))
    end)
end

-- 从资源树上获取其他组件管理的pcie_card对象
function c_device_service:get_other_pcie_cards()
    -- general_hardware: DPUCard
    local ok, obj_list = pcall(mdb.get_sub_objects, self.bus,
        '/bmc/kepler/Systems/1/PCIeDevices/PCIeCards/DPUCards', 'bmc.kepler.Systems.PCIeDevices.PCIeCard')
    if ok then
        return obj_list
    end
end

-- 找到同一级的pcie_device对象，返回bdf
function c_device_service:find_bdf(position)
    for _, v in pairs(self.pcie_device_list) do
        if position == v.position then
            return v:get_prop('Bus'), v:get_prop('Device'), v:get_prop('Function')
        end
    end
    return 0xff, 0xff, 0xff  -- 返回无效值
end

local function get_associated_resource(cpu_id)
    if cpu_id == 0xff then
        return 'PCIeSwitch'
    elseif cpu_id == 0xfe then
        return 'PCH'
    elseif cpu_id == 0xfd then
        return 'CPU1,CPU2'
    elseif cpu_id == 252 then
        return 'CPU1,CPU2,CPU3,CPU4'
    else
        return 'CPU' .. (cpu_id + 1)
    end
end

local function get_customer()
    local package_objs = client:GetPackageObjects()[PACKAGE_PATH]
    if package_objs == nil then
        log:error('[DeviceService] Get package objects failed')
        return ''
    end

    local customer = package_objs.Customer
    if customer == nil then
        log:error('[DeviceService] Get customer failed')
        return ''
    end

    return customer
end

function c_device_service:method_card_info_dump(ctx, path)
    local others = self:get_other_pcie_cards() or {}
    if not next(others) and not next(self.pcie_card_list) then
        return
    end

    log:notice('[DeviceService] App log dump.')
    local file = file_sec.open_s(path .. '/card_info', 'w+')
    if not file then
        log:error('[DeviceService] method_card_info_dump: open dir fail')
        return
    end

    utils.safe_close_file(file, function()
        file:write('Pcie Card Info\n')
        local buff = string.format('%-4s | %-10s | %-10s | %-15s | %-15s | %-12s | %-14s | %-16s | %-40s | %-10s |' ..
            ' %-14s | %-14s | %-8s | %-40s | %-40s | %-40s | %-40s\n', 'Slot', 'Vendor Id', 'Device Id',
            'Sub Vendor Id', 'Sub Device Id', 'Bus Number', 'Device Number', 'Function Number', 'Card Desc', 'Board Id',
            'PCB Version', 'CPLD Version', 'PartNum', 'Manufacturer', 'Position', 'AssociatedResource', 'ProductName')
        local format = '%-4u | 0x%04x     | 0x%04x     | 0x%04x          | 0x%04x          | 0x%02x' ..
            '         | 0x%02x           | 0x%02x             | %-40s | %-10s | %-14s | %-14s | %-8s | %-40s ' ..
            '| %-40s | %-40s | %-40s\n'
        for _, v in pairs(self.pcie_card_list) do
            local b, d, f = self:find_bdf(v.position)
            buff = buff .. string.format(format, v:get_prop('SlotID'), v:get_prop('VendorID'), v:get_prop('DeviceID'),
                v:get_prop('SubVendorID'), v:get_prop('SubDeviceID'), b, d, f,
                v:get_prop('Description'), v:get_prop('BoardID'), v:get_prop('PcbVersion'),
                'N/A', v:get_prop('PartNumber'), v:get_prop('Manufacturer'), v:get_prop('Position'),
                get_associated_resource(v:get_prop('LaneOwner')), v:get_prop('Name'))
            skynet.sleep(10)
        end
    
        for _, v in pairs(others) do
            local position = string.sub(v.path, string.find(v.path, '[0-9]+$'))
            local b, d, f = self:find_bdf(position)
            buff = buff .. string.format(format, v.SlotID, v.VendorID, v.DeviceID,
                v.SubVendorID, v.SubDeviceID, b, d, f,
                v.Description, v.BoardID, v.PcbVersion,
                'N/A', v.PartNumber, v.Manufacturer, v.Position,
                get_associated_resource(v.LaneOwner), v.Name)
            skynet.sleep(10)
        end
        file:write(buff)
    end)

    local customer = get_customer()
    if customer == 'Baidu' then
        file_sec.copy_file_s(path .. '/card_info', path .. '/pcie.info')
    end
end

-- 根据SSBDF查询PCIe设备
function c_device_service:get_pcie_device_by_ssbdf(segment, socket_id, bus, device, func, host_id)
    log:debug('get_pcie_device_by_ssbdf %s-%s-%s-%s-%s-%s', segment, socket_id, bus, device, func, host_id)
    -- 对于多host，需要根据SlotID区分。
    if host_id and host_id > 1 then
        local obj = cmn.find(self.pcie_device_list, function(item)
            if item.mds.Segment == segment and item.mds.SocketID == socket_id and item.mds.Bus == bus and
                item.mds.Device == device and item.mds.Function == func and
                item.mds.MultihostPresence & (1 << (host_id - 1)) > 0 then
                return true
            end
        end)
        return obj
    end

    local obj = cmn.find(self.pcie_device_list, function(item)
        if item.mds.Segment == segment and item.mds.SocketID == socket_id and item.mds.Bus == bus and
            item.mds.Device == device and item.mds.Function == func then
            return true
        end
    end)
    return obj
end

local RAS_STATUS_BITS_DEF = bs.new([[<<
    sys1:1/little,
    sys2:1/little,
    sys3:1/little,
    sys4:1/little,
    sys5:1/little,
    sys6:1/little,
    sys7:1/little,
    sys8:1/little
>>]])

-- RAS PCIE相关告警值根据最大8个system拆分为8bit
local function set_pcie_status_by_system(src_val, system_id, set_val)
    if system_id < 1 or system_id > 8 then
        log:error('invalid system_id = %s', system_id)
        return src_val
    end
    if set_val < 0 or set_val > 1 then
        log:error('invalid set_val = %s', set_val)
        return src_val
    end
    local src_val_str = string.pack('B', src_val)
    local val = RAS_STATUS_BITS_DEF:unpack(src_val_str)
    val['sys' .. system_id] = set_val
    return RAS_STATUS_BITS_DEF:pack(val):byte()
end

function c_device_service:method_set_diagnostic_fault(ctx, segment, socket_id, bus, device, func, value)
    local host_id = tonumber(ctx.SystemId) or 1
    local obj = self:get_pcie_device_by_ssbdf(segment, socket_id, bus, device, func, host_id)
    if not obj then
        return RET_ERR
    end
    local diagnosticfault_record = self.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(diagnosticfault_record.DiagnosticFault,
        host_id, value)
    diagnosticfault_record.DiagnosticFault = multi_value
    diagnosticfault_record:save()
    obj.mds.DiagnosticFault = multi_value
    return RET_OK
end

function c_device_service:method_set_predictive_fault(ctx, segment, socket_id, bus, device, func, value)
    local host_id = tonumber(ctx.SystemId) or 1
    local obj = self:get_pcie_device_by_ssbdf(segment, socket_id, bus, device, func, host_id)
    if not obj then
        return RET_ERR
    end
    local predictivefault_record = self.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(predictivefault_record.PredictiveFault,
        host_id, value)
    predictivefault_record.PredictiveFault = multi_value
    predictivefault_record:save()
    obj.mds.PredictiveFault = multi_value
    return RET_OK
end

local function set_pcie_correctable_error(handle, obj, value, host_id)
    local ce_record = handle.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(ce_record.CorrectableError,
        host_id, value)
    ce_record.CorrectableError = multi_value
    ce_record:save()
    obj.mds.CorrectableError = multi_value
    return RET_OK
end

local function set_pcie_uncorrectable_error(handle, obj, value, host_id)
    local uce_record = handle.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(uce_record.UncorrectableError,
        host_id, value)
    uce_record.UncorrectableError = multi_value
    uce_record:save()
    obj.mds.UncorrectableError = multi_value
    return RET_OK
end

local function set_pcie_fatal_error(handle, obj, value, host_id)
    local fe_record = handle.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(fe_record.FatalError,
        host_id, value)
    fe_record.FatalError = multi_value
    fe_record:save()
    obj.mds.FatalError = multi_value
    return RET_OK
end

local function set_pcie_parity_error(handle, obj, value, host_id)
    local perr_record = handle.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    if not perr_record then
        return RET_ERR
    end
    local multi_value = set_pcie_status_by_system(perr_record.ParityError,
        host_id, value)
    perr_record.ParityError = multi_value
    perr_record:save()
    obj.mds.ParityError = multi_value
    return RET_OK
end

local function set_pcie_system_error(handle, obj, value, host_id)
    local serr_record = handle.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    if not serr_record then
        return RET_ERR
    end
    local multi_value = set_pcie_status_by_system(serr_record.SystemError,
        host_id, value)
    serr_record.SystemError = multi_value
    serr_record:save()
    obj.mds.SystemError = multi_value
    return RET_OK
end

function c_device_service:method_set_error_status(ctx, segment, socket_id, bus, device, func, err_status, value)
    local host_id = tonumber(ctx.SystemId) or 1
    local obj = self:get_pcie_device_by_ssbdf(segment, socket_id, bus, device, func, host_id)
    if not obj then
        log:error("get pcie device by ssbdf failed.", value)
        return RET_ERR
    end
    if err_status == 'CorrectableError' then
        log:info("Set PCIe CorrectableError == %s.", value)
        return set_pcie_correctable_error(self, obj, value, host_id)
    elseif err_status == 'UncorrectableError' then
        log:info("Set PCIe UncorrectableError == %s.", value)
        return set_pcie_uncorrectable_error(self, obj, value, host_id)
    elseif err_status == 'FatalError' then
        log:info("Set PCIe FatalError == %s.", value)
        return set_pcie_fatal_error(self, obj, value, host_id)
    elseif err_status == 'ParityError' then
        log:info("Set PCIe ParityError == %s.", value)
        return set_pcie_parity_error(self, obj, value, host_id)
    elseif err_status == 'SystemError' then
        log:info("Set PCIe SystemError == %s.", value)
        return set_pcie_system_error(self, obj, value, host_id)
    end
end

-- 根据SBDF查询PCIe设备, 不使用Socket
function c_device_service:get_pcie_device_by_sbdf(segment, bus, device, func, host_id)
    -- 对于多host，需要根据SlotID区分。
    if host_id and host_id > 1 then
        local obj = cmn.find(self.pcie_device_list, function(item)
            if item.mds.Segment == segment and item.mds.Bus == bus and item.mds.Device == device and
                item.mds.Function == func and
                item.mds.MultihostPresence & (1 << (host_id - 1)) > 0 then
                return true
            end
        end)
        return obj
    end

    local obj = cmn.find(self.pcie_device_list, function(item)
        if item.mds.Segment == segment and item.mds.Bus == bus and item.mds.Device == device and
            item.mds.Function == func then
            return true
        end
    end)
    return obj
end

function c_device_service:method_set_uce_by_bios(ctx, segment, bus, device, func, value)
    local host_id = tonumber(ctx.SystemId) or 1
    local obj = self:get_pcie_device_by_sbdf(segment, bus, device, func, host_id)
    if not obj then
        return RET_ERR
    end
    
    local ucebybios_record = self.db.PCIeDevice({GroupPosition = obj.mds.GroupPosition})
    local multi_value = set_pcie_status_by_system(ucebybios_record.UCEByBIOS,
        host_id, value)
    ucebybios_record.UCEByBIOS = multi_value
    ucebybios_record:save()
    obj.mds.UCEByBIOS = multi_value
    return RET_OK
end

local function get_power_state(obj)
    local host_id = obj.reduction_event_list[1].host_id
    local FRUCTL_PATH<const> = '/bmc/kepler/Systems/' .. host_id .. '/FruCtrl'
    local FRUCTL_INTF = 'bmc.kepler.Systems.FruCtrl'
    local ok, fru_obj_list = pcall(mdb.get_sub_objects, obj.bus, FRUCTL_PATH, FRUCTL_INTF)
    if not ok then
        log:error('get power object failed')
        return nil
    end
    for _, fru_obj in pairs(fru_obj_list) do
        -- 目前资源树FruType为空无法区分HOST/DEV，默认查询第一个对象作为HOST的电源状态
        return fru_obj.PowerState
    end
    return nil
end

local function set_pcie_link_reduced(obj, event)
    -- 获取不到每次等待1s，总计3分钟获取不到PCIe device对象则认为获取不到该对象
    local pcie_device_obj = obj:get_pcie_device_by_sbdf(event.segment, event.bus, event.device, event.func,
        event.host_id)
    if not pcie_device_obj then
        cmn.skynet.sleep(100)
    end
    if pcie_device_obj ~= nil then
        -- 设置降带宽
        if event.fault_type == BANDWIDTH_REDUCTION then
            local bandwidthreduction_record = obj.db.PCIeDevice({
                GroupPosition = pcie_device_obj.mds.GroupPosition
            })
            local multi_value = set_pcie_status_by_system(
                bandwidthreduction_record.BandwidthReduction, event.host_id, event.value)
            bandwidthreduction_record.BandwidthReduction = multi_value
            bandwidthreduction_record:save()
            pcie_device_obj.mds.BandwidthReduction = multi_value

        -- 设置降速率
        elseif event.fault_type == LINK_SPEED_REDUCED then
            -- 告警持久化
            local link_speed_reduced_record = obj.db.PCIeDevice({
                GroupPosition = pcie_device_obj.mds.GroupPosition
            })
            local multi_value = set_pcie_status_by_system(
                link_speed_reduced_record.LinkSpeedReduced, event.host_id, event.value)
            link_speed_reduced_record.LinkSpeedReduced = multi_value
            link_speed_reduced_record:save()
            pcie_device_obj.mds.LinkSpeedReduced = multi_value
        else
            log:error('Invalid fault type of event')
        end
        return true
    end
end

local function update_reduction_event_count(obj, cur_event_obj, pcie_obj_found)
    for _, event_obj in pairs(obj.reduction_event_list) do
        event_obj.countdown = event_obj.countdown - 1
    end
    if pcie_obj_found or cur_event_obj.countdown <= 0 then
        table.remove(obj.reduction_event_list, 1) -- 已处理或超时
    else
        table.insert(obj.reduction_event_list, cur_event_obj) -- 放置末尾
        table.remove(obj.reduction_event_list, 1)
    end
end

-- 处理上报的链路降带宽/降速率事件
local function reduction_event_consumer(obj)
    local power_state, pcie_obj_found
    while true do
        if #obj.reduction_event_list > 0 then
            -- 首先获取电源状态，如果是下电状态就不产生告警，并且把告警队列中的告警删除
            power_state = get_power_state(obj)
            if power_state == nil or power_state == 'OFF' then
                -- 释放消息队列中的所有告警，避免下电状态误告警
                obj.reduction_event_list = {}
            else
                pcie_obj_found = set_pcie_link_reduced(obj, obj.reduction_event_list[1])
                update_reduction_event_count(obj, obj.reduction_event_list[1], pcie_obj_found)
            end
        else
            cmn.skynet.wait()
        end
    end
end

function c_device_service:method_set_bandwidth_reduction(ctx, segment, bus, device, func, value)
    local host_id = tonumber(ctx.SystemId) or 1
    -- 将相关的属性设置插入到队列中
    local bandwidth_event = {
        segment = segment,
        bus = bus,
        device = device,
        func = func,
        value = value,
        fault_type = BANDWIDTH_REDUCTION,
        host_id = host_id,
        countdown = 180  -- 最多存在3分钟
    }

    table.insert(self.reduction_event_list, bandwidth_event)
    if #self.reduction_event_list == 1 then
        cmn.skynet.wakeup(self.reduction_event_co)
    end
    return RET_OK
end

function c_device_service:method_set_link_speed_reduced(ctx, segment, bus, device, func, value)
    local host_id = tonumber(ctx.SystemId) or 1
    -- 将相关的属性设置插入到队列中
    local link_speed_event = {
        segment = segment,
        bus = bus,
        device = device,
        func = func,
        value = value,
        fault_type = LINK_SPEED_REDUCED,
        host_id = host_id,
        countdown = 180  -- 最多存在3分钟
    }
    table.insert(self.reduction_event_list, link_speed_event)
    if #self.reduction_event_list == 1 then
        cmn.skynet.wakeup(self.reduction_event_co)
    end
    return RET_OK
end

function c_device_service:method_get_device_name(ctx, segment, socket_id, bus, device, func)
    local host_id = tonumber(ctx.SystemId) or 1
    local ok, obj = pcall(function()
        return self:get_pcie_device_by_ssbdf(segment, socket_id, bus, device, func, host_id)
    end)
    if not ok or not obj then
        log:error('method_get_device_name fail')
        return RET_ERR, NULL_VAL
    end
    return RET_OK, obj.mds.DeviceName
end

function c_device_service:method_get_pcie_card_num()
    return #self.pcie_card_list
end

function c_device_service:set_fault_status_by_bios(ctx, card_bus, card_device, card_function,
    fault_status)
    local host_id = tonumber(ctx.SystemId) or 1
    for _, pcie_device_obj in pairs(self.pcie_device_list) do
        if pcie_device_obj.Bus == card_bus and pcie_device_obj.Device == card_device and
            pcie_device_obj.Function == card_function and
            pcie_device_obj.MultihostPresence & (1 << (host_id - 1)) > 0 then
            pcie_device_obj.FaultByBios = fault_status
            return 0
        end
    end
    return -1
end

function c_device_service:init()
    self.service_manager:register('device', self)
end

function c_device_service:ctor(bus, db, service_manager)
    self.bus = bus
    self.db = db
    self.service_manager = service_manager
    self.pcie_device_list = {}
    self.pcie_card_list = {}
    self.reduction_event_list = {}
    self.pcie_oob_mgmt_flag = false
    self.reduction_event_co = cmn.skynet.fork_loop({count = 0}, function()
        reduction_event_consumer(self)
    end)
end

return singleton(c_device_service)
