-- 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: 负责监听BIOS上报的PCIe设备信息并完成加载
local class = require 'mc.class'
local log = require 'mc.logging'
local cmn = require 'common'
local ctx = require 'mc.context'
local def = require 'biz_topo.def'
local c_load_info = require 'biz_topo.class.load_info'
local object_manage = require 'mc.mdb.object_manage'
local m_error = require 'pcie_device.errors'
local imu_cmd = require 'imu'
local utils_core = require 'utils.core'
local c_device_service = require 'device.device_service'

local UINT8_MAX<const> = 0xff
local UINT16_MAX<const> = 0xffff
local UINT32_MAX<const> = 0xffffffff

local OBJGRP_PATH_PREFIX<const> = '/bmc/kepler/ObjectGroup/'
local CSR_DIR<const> = "/opt/bmc/sr"
local FLASH_ERROR_ID<const> = "fffffffe"
local FLASH_ERROR_AUX_ID<const> = "ffffffff"

---@class DeviceLoader @PCIe设备加载
local c_device_loader = class()

-- 从持久化数据中加载PCIe设备
function c_device_loader:load_device_by_per_info(position)
    local pcie_biz_connectors = self.biz_topo:get_objs('PCIeBizConnector', position)
    for _, c in pairs(pcie_biz_connectors) do
        -- 加载的设备类型合法性校验
        local type = nil
        if c.ref_pcie_addr_info then
            type = def.com_type_to_type[c.ref_pcie_addr_info:get_prop('ComponentType')]
        end
        if not type or not self.persistent_load_info[type] then
            log:debug('[BizTopoLoader] Load failed, invalid type, type=%s, position=%s', type,
                position)
            goto next
        end

        -- 从持久化数据中查找该槽位的PCIe设备记录，找到则进行加载
        local record = self.persistent_load_info[type][c.ref_pcie_addr_info:get_prop('SlotID')]
        if not record then
            goto next
        end

        log:debug('[BizTopoLoader] load_device_by_per_info record.DevBus=%s', record.DevBus)
        c.ref_pcie_addr_info:update_device_bdf(record.DevBus, record.DevDevice, record.DevFunction)
        c.ref_pcie_addr_info:update_multihost_presence(record.MultihostPresence or 1)
        c.ref_pcie_addr_info:update_vid_did(record.ID >> 16 & UINT16_MAX, record.ID & UINT16_MAX)

        self:task_load_unload_device(c_load_info.new(type, record.SlotID, record.ID, record.AuxID),
            record.MultihostPresence, position, type)

        ::next::
    end
end

local function get_pcie_vid_did_info(sys_id, bus, pcie_info)
    local payload = {
        system_id = 1,
        is_local = false,
        cpu_id = pcie_info.bus_info.socket_id,
        bus_num = pcie_info.bus_info.bus,
        device_num = pcie_info.bus_info.device,
        function_num = pcie_info.bus_info.func,
        address = imu_cmd.pci_info_address_list.VID_DID_INFO_ADDR,
        read_length = 4
    }
    local info = imu_cmd.get_info_from_pmu(sys_id, bus, payload)
    if info == nil or #info < 4 then
        error(m_error.get_pci_info_failed())
    end
    local device_id = ((info[4] & 0xff) << 8) + (info[3] & 0xff)
    local vendor_id = ((info[2] & 0xff) << 8) + (info[1] & 0xff)
    return vendor_id, device_id
end

local function get_pcie_sub_vid_did_info(sys_id, bus, pcie_info)
    local payload = {
        system_id = 1,
        is_local = false,
        cpu_id = pcie_info.bus_info.socket_id,
        bus_num = pcie_info.bus_info.bus,
        device_num = pcie_info.bus_info.device,
        function_num = pcie_info.bus_info.func,
        address = imu_cmd.pci_info_address_list.SUBVID_SUBDID_INFO_ADDR,
        read_length = 4
    }
    local info = imu_cmd.get_info_from_pmu(sys_id, bus, payload)
    if info == nil or #info < 4 then
        error(m_error.get_pci_info_failed())
    end
    local sub_device_id = ((info[4] & 0xff) << 8) + (info[3] & 0xff)
    local sub_vendor_id = ((info[2] & 0xff) << 8) + (info[1] & 0xff)
    return sub_vendor_id, sub_device_id
end

-- 通过BDF查询vid/did/sub_vid/sub_did
local function query_id_from_pmu(sys_id, bus, info)
    if info.type == def.device_type.DISK then
        return
    end

    local err, vid, did, sub_vid, sub_did
    err, vid, did = pcall(get_pcie_vid_did_info, sys_id, bus, info)
    if not err then
        log:error('[BizTopoLoader] Get vid/did from pmu failed, %s', err)
        return
    end
    err, sub_vid, sub_did = pcall(get_pcie_sub_vid_did_info, sys_id, bus, info)
    if not err then
        log:error('[BizTopoLoader] Get subvid/subdid from pmu failed, %s', err)
        return
    end
    info.vid = vid
    info.did = did
    info.sub_vid = sub_vid
    info.sub_did = sub_did
    log:notice('[BizTopoLoader] Get id from PMU, vid:%s, did:%s, sub_vid:%s, sub_did:%s', info.vid,
        info.did, info.sub_vid, info.sub_did)
end

-- 卸载卡
local function unload_device_connector(load_info, connector, addr_multi_presence)
    -- 软件类告警消除需要component存在，卡卸载前需要消除软件类告警
    if connector.Id == FLASH_ERROR_ID and connector.AuxId == FLASH_ERROR_AUX_ID then
        c_device_service.get_instance():clear_flash_checker_event(load_info.slot_id)
    end

    if addr_multi_presence == 0 then
        object_manage.remove_objects(OBJGRP_PATH_PREFIX .. connector.GroupPosition)
        connector.Presence = 0
        log:notice('[BizTopoLoader] Unload %s, Slot=%s', load_info.type, load_info.slot_id)
    end
end

-- 从硬件获取下级设备的slot位置
local function set_slot_from_load_info(connector, slot_id)
    if not slot_id or slot_id < def.PCIE_SLOT_ID_VALUE.MIN_SLOT_ID_VALUE or 
        slot_id > def.PCIE_SLOT_ID_VALUE.MAX_SLOT_ID_VALUE then
        log:error('set pcie decvice slot_id=%s failed, '..
                'correct value %s< slot_id <%s', slot_id, def.PCIE_SLOT_ID_VALUE.MIN_SLOT_ID_VALUE,
                def.PCIE_SLOT_ID_VALUE.MAX_SLOT_ID_VALUE)
        return
    end 
    local ok, err = pcall(function ()
        connector.Slot = slot_id
    end)
    if not ok then
        log:error('set slot from load info failed, %s', err)
    end
end

-- 对比传入的加载信息和Connector对象上已有的信息是否相同，决定PCIe设备的卸载/加载
---@param load_info LoadInfo @传入的PCIe设备加载信息
function c_device_loader:load_unload_device(load_info, position, is_timeout, addr_multi_presence)
    local ok, connector, tc_connector = self.biz_topo:get_mgmt_connector(load_info.type, load_info.slot_id, position)
    if not ok then
        return false, connector
    end
    if tc_connector then
        set_slot_from_load_info(tc_connector, load_info.slot_id)
        if tc_connector.Presence == 1 and tc_connector.LoadStatus == 0 then
            -- 天池加载成功
            log:notice('[BizTopoLoader] TianChiConnector for slot=%s is already loaded, \
                path=%s, Status=%s, Presence=%s, Id-AuxId=%s-%s, skip loading', load_info.slot_id, tc_connector.path,
                tc_connector.LoadStatus, tc_connector.Presence, tc_connector.Id, tc_connector.AuxId)
            return true
        elseif tc_connector.LoadStatus ~= 0 and tc_connector.LoadStatus ~= 255 then
            -- 天池加载出错，用四元组方式加载
            log:notice('[BizTopoLoader] TianChiConnector for slot=%s is not loaded, \
                path=%s, Status=%s, Presence=%s, Id-AuxId=%s-%s, using normal connector', load_info.slot_id,
                tc_connector.path, tc_connector.LoadStatus, tc_connector.Presence, tc_connector.Id, tc_connector.AuxId)
        elseif not is_timeout then
            -- 如果没有超时，就继续等待天池连接器状态就绪
            return false, def.load_error.TIANCHI_CONNECTOR_NOT_READY
        end
    end

    -- 对比Connector上已有属性值，无变化则无需重复加载
    local str_id, str_auxid = self:get_id_auxid_str(connector.Bom, load_info)
    log:notice('[BizTopoLoader] Connector is detected, Slot=%s, path=%s, Id-AuxId=%s-%s, load-Id-AuxId=%s-%s',
        load_info.slot_id, connector.path, connector.Id, connector.AuxId, str_id, str_auxid)
    if connector.Id == str_id and connector.AuxId == str_auxid and connector.Presence == 1 then
        log:notice('[BizTopoLoader] Connector is already loaded, Slot=%s', load_info.slot_id)
        return true
    end

    -- 兼容multihost卸载卡
    unload_device_connector(load_info, connector, addr_multi_presence)

    -- 有变化先卸载，再加载
    if load_info.id ~= UINT32_MAX then -- 0xffffffff表示没有卡在位
        object_manage.remove_objects(OBJGRP_PATH_PREFIX .. connector.GroupPosition)
        connector.Presence = 0
        log:notice('[BizTopoLoader] Unload %s, Slot=%s', load_info.type, load_info.slot_id)

        set_slot_from_load_info(connector, load_info.slot_id)
        cmn.skynet.sleep(100 * 1) -- 1s延迟
        connector.Id = str_id
        connector.AuxId = str_auxid
        connector.Presence = 1
        log:notice('[BizTopoLoader] Load %s, Slot=%s, path=%s, Id-AuxId=%s-%s', load_info.type,
            load_info.slot_id, connector.path, connector.Id, connector.AuxId)
    else
        -- 卡不在位清除软件告警
        c_device_service.get_instance():clear_flash_checker_event(load_info.slot_id)
    end
    return true
end

function c_device_loader:get_id_auxid_str(bom, load_info)
    if not bom or not load_info or not load_info.id or not load_info.aux_id then
        return '', ''
    end

    local str_id = string.format('%08x', load_info.id)
    local str_auxid = string.format('%08x', load_info.aux_id)
    local file_path = string.format('%s/%s_%s_%s.sr', CSR_DIR, bom, str_id, str_auxid)
    -- pcie卡在位且sr文件不存在，加载默认错误的csr
    if load_info.type == def.device_type.PCIE_CARD and load_info.id ~= UINT32_MAX and
        not utils_core.is_file(file_path) then
        return FLASH_ERROR_ID, FLASH_ERROR_AUX_ID
    end
    return str_id, str_auxid
end

function c_device_loader:task_load_unload_device(load_info, addr_multi_presence, position, device_type)
    if device_type == def.device_type.DISK then
        return -- nvme不需要四元组加载
    end

    cmn.skynet.fork(function()
        local timeout = 120 -- 加载超时时间120s
        local tc_timeout = 100 -- 等待天池连接器超时时间100s
        local ok, ret
        repeat
            ok, ret = self:load_unload_device(load_info, position, tc_timeout <= 0, addr_multi_presence)
            cmn.skynet.sleep(500) -- 等待5秒
            timeout = timeout - 5
            tc_timeout = tc_timeout - 5
            -- 满足以下3个条件任意之一，停止加载任务
            -- 1.加载成功
            -- 2.没有匹配到业务连接器或指定槽位的PCIeAddrInfo对象
            -- 3.超时
            if timeout <= 0 then
                log:error('[BizTopoLoader] Load %s time out, slot=%s', load_info.type,
                    load_info.slot_id)
                return
            end
            if not ok then
                if ret == def.load_error.NO_MATCHING_BUSINESS_CONNECTOR then
                    log:error('[BizTopoLoader] No matching BusinessConnector found, position=%s',
                        position)
                    return
                end
                if ret == def.load_error.NO_MATCHING_PCIE_ADDR_INFO then
                    log:debug('[BizTopoLoader] No matching PCIeAddrInfo found, type=%s, slot=%s',
                        load_info.type, load_info.slot_id)
                    return
                end
                if ret == def.load_error.TIANCHI_CONNECTOR_NOT_READY then
                    log:debug('[BizTopoLoader] TianChi Connector is not ready, position=%s', position)
                end
            end
        until ok
    end)
end

-- 获取持久化pcie卡加载信息
local function get_load_info_records(db_handle, tbl)
    local records = {}
    if not tbl then
        return records
    end
    local table_disk = db_handle:select(tbl):where(tbl.Type:eq(def.device_type.DISK)):all()
    local table_pcie = db_handle:select(tbl):where(tbl.Type:eq(def.device_type.PCIE_CARD)):all()
    local table_ocp = db_handle:select(tbl):where(tbl.Type:eq(def.device_type.OCP_CARD)):all()
    for _, v in pairs(table_disk) do
        table.insert(records, v)
    end
    for _, v in pairs(table_pcie) do
        table.insert(records, v)
    end
    for _, v in pairs(table_ocp) do
        table.insert(records, v)
    end
    return records
end

-- 判断是否需要拷贝本地持久化数据
local function need_recover_from_local(local_records)
    -- 如果是有RecoverFlag，就需要还原
    if local_records and local_records[1] and local_records[1].RecoverFlag == 1 then
        return true
    end
    return false
end

-- 更新本地持久化数据到远程持久化
local function recover_remote_db(local_records, remote_db_handle, remote_records)
    -- 删除所有远程持久化数据
    for _, remote_record in pairs(remote_records) do
        remote_record:delete()
    end

    local new_remote_record
    for _, local_record in pairs(local_records) do
        if local_record.DevBus == UINT8_MAX and local_record.DevDevice == UINT8_MAX and
            local_record.DevFunction == UINT8_MAX then
            log:notice('[BizTopoLoader] skip empty record')
            goto next
        end

        new_remote_record = remote_db_handle.PCIeLoadInfo({
            Type = local_record.Type,
            SlotID = local_record.SlotID,
            ID = local_record.ID,
            AuxID = local_record.AuxID,
            Segment = local_record.Segment,
            SocketID = local_record.SocketID,
            DevBus = local_record.DevBus,
            DevDevice = local_record.DevDevice,
            DevFunction = local_record.DevFunction,
            MultihostPresence = local_record.MultihostPresence
        })
        new_remote_record:save()
        log:notice('[BizTopoLoader] Add persistance PCIeLoadInfo, Type=%s, SlotID=%s, DevBus=%s, MultiPresence=%s',
            new_remote_record.Type, new_remote_record.SlotID, new_remote_record.DevBus,
            new_remote_record.MultihostPresence)
        ::next::
        -- 删除本地持久化数据，只用一次。
        local_record:delete()
    end
end

-- 使用远程持久化数据，初始化PCIe加载信息
function c_device_loader:persistent_load_info_init()
    local remote_db_handle = self.db
    local remote_records = get_load_info_records(remote_db_handle, remote_db_handle.PCIeLoadInfo)

    self.persistent_load_info = {
        [def.device_type.DISK] = {},
        [def.device_type.PCIE_CARD] = {},
        [def.device_type.OCP_CARD] = {}
    }
    for _, record in pairs(remote_records) do
        if self.persistent_load_info[record.Type] then
            self.persistent_load_info[record.Type][record.SlotID] = record
            log:notice(
                '[BizTopoLoader] Get persistance PCIeLoadInfo, Type=%s, SlotID=%s, DevBus=%s',
                record.Type, record.SlotID, record.DevBus)
        end
    end
end

-- 读取持久化PCIe加载信息
function c_device_loader:pcie_load_info_init()
    -- 兼容旧版本使用远程持久化数据
    local local_db_handle = self.reset_local_db
    local local_records = get_load_info_records(local_db_handle, local_db_handle.PCIeLoadInfoBackup)

    local remote_db_handle = self.db
    local remote_records = get_load_info_records(remote_db_handle, remote_db_handle.PCIeLoadInfo)

    -- 判断是否需要更新本地持久化数据到远程持久化
    if need_recover_from_local(local_records) then
        recover_remote_db(local_records, remote_db_handle, remote_records)
    end

    -- 使用远程持久化数据
    self:persistent_load_info_init()
end

-- 还原/恢复出厂设置的时候，备份当前pcie卡信息到本地持久化数据。
function c_device_loader:on_recover_cb()
    local local_db_handle = self.reset_local_db
    local local_records = get_load_info_records(local_db_handle, local_db_handle.PCIeLoadInfoBackup)
    local remote_db_handle = self.db
    local remote_records = get_load_info_records(remote_db_handle, remote_db_handle.PCIeLoadInfo)
    -- 删除所有本地持久化数据
    for _, local_record in pairs(local_records) do
        local_record:delete()
    end

    local new_local_record
    for _, remote_record in pairs(remote_records) do
        new_local_record = local_db_handle.PCIeLoadInfoBackup({
            Type = remote_record.Type,
            SlotID = remote_record.SlotID,
            ID = remote_record.ID,
            AuxID = remote_record.AuxID,
            Segment = remote_record.Segment,
            SocketID = remote_record.SocketID,
            DevBus = remote_record.DevBus,
            DevDevice = remote_record.DevDevice,
            DevFunction = remote_record.DevFunction,
            MultihostPresence = remote_record.MultihostPresence,
            RecoverFlag = 1
        })
        new_local_record:save()
        log:notice('[BizTopoLoader] Add PCIeLoadInfoBackup, Type=%s, SlotID=%s, DevBus=%s, MultiPresence=%s',
            new_local_record.Type, new_local_record.SlotID, new_local_record.DevBus,
            new_local_record.MultihostPresence)
    end

    -- 当前没有pcie卡信息就存一条无效数据
    if #remote_records == 0 then
        new_local_record = local_db_handle.PCIeLoadInfoBackup({
            Type = def.device_type.PCIE_CARD,
            SlotID = 0,
            ID = UINT32_MAX,
            AuxID = UINT32_MAX,
            Segment = 0,
            SocketID = 0,
            DevBus = UINT8_MAX,
            DevDevice = UINT8_MAX,
            DevFunction = UINT8_MAX,
            MultihostPresence = 0,
            RecoverFlag = 1
        })
        new_local_record:save()
        log:notice('[BizTopoLoader] Add PCIeLoadInfoBackup empty record.')
    end
end

-- 新增未持久化的记录
local function add_device_load_info(sys_id, tbl, new_pcie_load_info, per_load_info)
    for _, info in pairs(new_pcie_load_info) do
        if not info.completed then
            local record = tbl({
                Type = info.type,
                SlotID = info.slot_id,
                ID = info.id,
                AuxID = info.aux_id,
                Segment = info.bus_info.segment,
                SocketID = info.bus_info.socket_id,
                DevBus = info.bus_info.bus,
                DevDevice = info.bus_info.device,
                DevFunction = info.bus_info.func,
                MultihostPresence = 1 << (sys_id - 1)
            })
            record:save()
            per_load_info[record.SlotID] = record
            log:error('[BizTopoLoader] Add persistance PCIeLoadInfo, Type=%s, SlotID=%s, DevBus=%s, MultiPresence=%s',
                record.Type, record.SlotID, record.DevBus, record.MultihostPresence)
        end
    end
end

---@param new_pcie_load_info LoadInfo[] @单次上报的全量PCIe设备加载信息集合
function c_device_loader:device_load_info_persist(sys_id, type, new_pcie_load_info)
    local per_load_info = self.persistent_load_info[type]
    for _, record in pairs(per_load_info) do
        local info = new_pcie_load_info[record.SlotID]
        -- 刷新持久化记录
        if info then
            log:notice('[BizTopoLoader] Update persistance PCIeLoadInfo, Type=%s, SlotID=%s, DevBus=%s',
                record.Type, record.SlotID, record.DevBus)
            record.ID = info.id
            record.AuxID = info.aux_id
            record.Segment = info.bus_info.segment
            record.SocketID = info.bus_info.socket_id
            record.DevBus = info.bus_info.bus
            record.DevDevice = info.bus_info.device
            record.DevFunction = info.bus_info.func
            record.MultihostPresence = record.MultihostPresence | (1 << (sys_id - 1))
            record:save()
            info.completed = true
        else
            -- 对应host的在位设为0
            record.MultihostPresence = record.MultihostPresence & (~(1 << (sys_id - 1)))
            if record.MultihostPresence == 0 then
                -- 删除不存在的持久化记录
                log:notice('[BizTopoLoader] Delete persistance PCIeLoadInfo, Type=%s, SlotID=%s, DevBus=%s',
                    record.Type, record.SlotID, record.DevBus)
                local slot_id = record.SlotID
                record:delete()
                per_load_info[slot_id] = nil
            else
                record:save()
            end
        end
    end

    -- 新增未持久化的记录
    add_device_load_info(sys_id, self.db.PCIeLoadInfo, new_pcie_load_info, per_load_info)
end

-- 解析Bios上报的Device BDF信息
---@return LoadInfo[]
local function parse_pcie_card_bdf_data(data, device_type)
    log:notice('[BizTopoLoader] Parse Card BDF')
    if not data then
        log:error('[BizTopoLoader] The bus info data is in valid.')
    end

    local pcie_card_info = {}
    local index = 1

    -- nvme的SlotID是从0开始。
    if device_type == def.device_type.DISK then
        log:info('parse_pcie_card_bdf_data change index to 0.')
        index = 0
    end

    local segment, socket_id, bus, device, func, ok, info
    -- 5个字节为一组bdf信息
    for i = 1, 255, 5 do
        ok = pcall(function()
            segment, socket_id, bus, device, func = string.unpack('BBBBB', data, i)
        end)
        if not ok then
            break
        end
        -- 只取segmenet为0的
        if segment == 0 then
            info = c_load_info.new(device_type, index, nil, nil,
                {segment, socket_id, bus, device, func})
            pcie_card_info[index] = info
            if socket_id ~= 0xff or bus ~= 0xff or device ~= 0xff or func ~= 0xff then
                log:notice(
                    '[BizTopoLoader] type=%s, slot_id=%d - DeviceSSBDF=[0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x]',
                    info.type, index, segment, socket_id, bus, device, func)
            end
        end
        index = index + 1
    end
    return pcie_card_info
end

local function check_update_multi_presence(sys_id, addr, info)
    if not addr then
        log:error('[BizTopoLoader] check_update_multi_presence, no addr')
        return
    end
    local addr_multi_presence = addr:get_prop('MultihostPresence')
    if info and (info.id ~= UINT32_MAX or info.type == def.device_type.DISK) then
        -- 卡在位
        addr_multi_presence = addr_multi_presence | (1 << (sys_id - 1))
        addr:update_device_bdf(info.bus_info.bus, info.bus_info.device, info.bus_info.func)
    else
        -- 卡不在位
        addr_multi_presence = addr_multi_presence & (~(1 << (sys_id - 1)))
        if addr_multi_presence == 0 then
            -- 全部host不在位，就置为ff
            addr:update_device_bdf(info.bus_info.bus, info.bus_info.device, info.bus_info.func)
        end
    end
    addr:update_multihost_presence(addr_multi_presence)
    addr:update_vid_did(info.vid, info.did)
    return addr_multi_presence
end

function c_device_loader:load_device(sys_id, raw_data, device_type)
    log:notice('[BizTopoLoader] Bios %s BDF start. sys_id=%s', device_type, sys_id)
    if not raw_data then
        log:error('[BizTopoLoader] %s Raw data is invalid. sys_id=%s', device_type, sys_id)
        return nil
    end

    local data = raw_data:value()
    local pcie_card_info_total = parse_pcie_card_bdf_data(data, device_type)
    local new_pcie_load_info = {}
    local ok, ret, addr_multi_presence
    for _, info in pairs(pcie_card_info_total) do
        -- 校验总线信息的合法性
        info.vid = UINT16_MAX
        info.did = UINT16_MAX
        info.sub_vid = UINT16_MAX
        info.sub_did = UINT16_MAX
        if (info.bus_info.bus == UINT8_MAX and info.bus_info.device == UINT8_MAX and
            info.bus_info.func == UINT8_MAX) or info.bus_info.bus == 0 then
            info.vid = UINT16_MAX -- 不做处理
        else
            query_id_from_pmu(sys_id, self.bus, info)
        end
        info.id = (info.vid << 16) + info.did
        info.aux_id = (info.sub_vid << 16) + info.sub_did

        -- 更新PCIeAddrInfo对象的DeviceBDF信息
        ok, ret = self.biz_topo:get_pcie_addr_info(info.type, info.slot_id)
        if ok then
            addr_multi_presence = check_update_multi_presence(sys_id, ret.addr, info)
            log:notice('[BizTopoLoader] addr_multi_presence=%s, sys_id=%s', addr_multi_presence, sys_id)
        end

        self:task_load_unload_device(info, addr_multi_presence, nil, device_type)

        if info.id ~= UINT32_MAX or device_type == def.device_type.DISK then
            new_pcie_load_info[info.slot_id] = info
        end
    end
    -- 持久化加载信息
    self:device_load_info_persist(sys_id, device_type, new_pcie_load_info)
end

-- 响应BIOS上报的BDF信息
function c_device_loader:on_pcie_card_bdf_changed(values, path, interface)
    if not values or
        (not values['PcieCardBDF'] and not values['OCPCardBDF'] and not values['PcieDiskBDF']) then
        return
    end
    local sys_id = tonumber(string.match(path or '', '/bmc/kepler/Systems/(%d+)/Bios'))
    log:notice('[BizTopoLoader] Bios BDF changed. sys_id=%s', sys_id)

    local raw_datas = {
        [def.device_type.DISK] = values['PcieDiskBDF'],
        [def.device_type.PCIE_CARD] = values['PcieCardBDF'],
        [def.device_type.OCP_CARD] = values['OCPCardBDF']
    }
    local ok, ret
    for device_type, raw_data in pairs(raw_datas) do
        ok, ret = pcall(function()
            return self:load_device(sys_id, raw_data, device_type)
        end)
        if not ok then
            log:error("[BizTopoLoader] load_device error, ret:%s, sys_id:%s", ret, sys_id)
        end
    end
end

function c_device_loader:ctor(bus, db, biz_topo, reset_local_db)
    self.bus = bus
    self.db = db
    self.reset_local_db = reset_local_db
    self.biz_topo = biz_topo
    self.persistent_load_info = {}
end

function c_device_loader:main()
    self:pcie_load_info_init()
end

-- Test
c_device_loader.parse_pcie_card_bdf_data = parse_pcie_card_bdf_data
c_device_loader.get_pcie_vid_did_info = get_pcie_vid_did_info
c_device_loader.unload_device_connector = unload_device_connector

return c_device_loader
