-- Copyright (c) 2025 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local comm_defs = require 'device_mgmt.comm_defs'
local comm_fun = require 'device_mgmt.comm_fun'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local gpu_service = require 'gpu_service.gpu_service'
local clsmgmt = require 'mc.class_mgnt'
local cmn = require 'common'

local gpu_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local PROP_TABLE = {
    ["bmc.dev.PCIeDevice"] = {
        ['Slot'] = function(resource_obj, v)
            resource_obj["Slot"] = tostring(v)
            return resource_obj["Slot"] == tostring(v)
        end,
        ['VendorId'] = function(resource_obj, v)
            resource_obj["VendorID"] = tonumber(v)
            return resource_obj["VendorID"] == tonumber(v)
        end,
        ['DeviceId'] = function(resource_obj, v)
            resource_obj["DeviceID"] = tonumber(v)
            return resource_obj["DeviceID"] == tonumber(v)
        end,
        ['SubSystemVendorId'] = function(resource_obj, v)
            resource_obj["SubVendorID"] = tonumber(v)
            return resource_obj["SubVendorID"] == tonumber(v)
        end,
        ['SubSystemDeviceId'] = function(resource_obj, v)
            resource_obj["SubDeviceID"] = tonumber(v)
            return resource_obj["SubDeviceID"] == tonumber(v)
        end,
    },
    ["bmc.dev.PCIeCard"] = {
        ['PartNumber'] = 'BoardPartNumber'
    },
    ["bmc.dev.Processor"] = {
        ['ProcessorType'] = 'ProcessorType',
        ['SystemId'] = 'SystemId',
        ['Id'] = 'Id',
        ['Presence'] = 'Presence',
        ['InstructionSet'] = 'InstructionSet',
        ['Architecture'] = 'Architecture',
        ['Manufacturer'] = 'Manufacturer',
        ['Family'] = 'Family',
        ['Model'] = 'Model',
        ['PartNumber'] = 'PartNumber',
        ['SerialNumber'] = 'SN',
        ['FirmwareVersion'] = 'FirmwareVersion',
        ['SocketDesignation'] = 'SocketDesignation',
        ['Location'] = 'Position'
    },
    ["bmc.dev.Gpu"] = {
        ['InfoRomVersion'] = 'InfoRomVersion',
        ['BuildDate'] = 'BuildDate',
        ['UUID'] = 'UUID',
        ['GPUUtilization'] = 'Utilization',
        ['PrimaryGPUTemperatureCelsius'] = 'TemperatureCelsius'
    },
    ["bmc.dev.Gpu.Power"] = {
        ['PowerWatts'] = 'PowerWatts',
        ['PowerBrakeState'] = 'PowerBrakeSet',
        ['ExternalPowerSufficient'] = 'ExternalPowerSufficient'
    },
    ["bmc.dev.Gpu.Status"] = {
        ['ECCModeEnabled'] = 'ECCModeEnabled',
        ['ECCModePendingEnabled'] = 'ECCModePendingEnabled',
        ['ResetRequired'] = 'ResetRequired',
        ['LinkInfo'] = 'NvLinkInfo'
    },
    ["bmc.dev.Memory"] = {
        ['Manufacturer'] = 'MemoryVendor',
        ['PartNumber'] = 'MemoryPartNumber',
        ['Utilization'] = 'MemoryUtilization',
        ['DoubleBitErrorPageCount'] = 'DoubleBitErrorPageCount',
        ['SingleBitErrorPageCount'] = 'SingleBitErrorPageCount'
    }
}

-- 同步设备树属性到资源树对象
function gpu_mgmt:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value)
    if PROP_TABLE[interface] and PROP_TABLE[interface][prop] then
        local ok
        repeat
            log:info('[device_mgmt] sync_dev_prop_to_resource_obj intf=%s prop=%s value=%s', interface, prop, value)
            if type(PROP_TABLE[interface][prop]) == 'string' then
                resource_obj[PROP_TABLE[interface][prop]] = value
                ok = resource_obj[PROP_TABLE[interface][prop]] == value
            else
                ok = PROP_TABLE[interface][prop](resource_obj, value)
            end
        until ok
    end
end

function gpu_mgmt:listen_device_obj_property_change(bus, sig_slot, device_path, resource_obj)
    log:info('[device_mgmt]listen_device_obj_property_change start, device_path is %s', device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        local sync_ok, sync_ret
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            sync_ok, sync_ret = pcall(function()
                self:sync_dev_prop_to_resource_obj(resource_obj, interface, k, v:value())
            end)
            if not sync_ok then
                log:error('[device_mgmt] listen_device_obj_property_change error=%s', sync_ret)
            end
        end
    end)
end

function gpu_mgmt:synchronize_property(bus, device_path, resource_obj)
    log:info('[device_mgmt]synchronize_property start, device_path is %s', device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path,
        { comm_defs.PCIE_DEVICE_INTERFACE, comm_defs.PCIE_CARD_INTERFACE, comm_defs.PROCESSOR_INTERFACE, 
        comm_defs.GPU_INTERFACE, comm_defs.GPU_POWER_INTERFACE, comm_defs.GPU_STATUS_INTERFACE,
        comm_defs.MEMORY_INTERFACE })
    if not ret then
        return
    end
    local device_obj, props
    local sync_ok, sync_ret
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface)
            device_obj = mdb.get_object(bus, device_path, interface)
            props = bus:call(service_name, device_path, comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
                sync_ok, sync_ret = pcall(function()
                    self:sync_dev_prop_to_resource_obj(resource_obj, interface, prop,
                        comm_fun.get_prop(device_obj, prop))
                end)
                if not sync_ok then
                    log:error('[device_mgmt] listen_device_obj_property_change error=%s', sync_ret)
                end
            end
        end
    end
end

function gpu_mgmt:get_object_name_by_device_path(bus, device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path, { comm_defs.OBJECT_PROPERTIES_INTERFACE })
    if not ret then
        return
    end
    local device_obj
    for ret_key, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            log:info('[device_mgmt]get_object_name_by_device_path ret_key=%s interface=%s', ret_key, interface)
            mdb.register_interface(interface)
            device_obj = mdb.get_object(bus, device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not device_obj then
        log:error('[device_mgmt]get_object_name_by_device_path failed, device_path is %s', device_path)
        return
    end
    local object_name = comm_fun.get_prop(device_obj, 'ObjectName')
    log:info('[device_mgmt]get_object_name_by_device_path object_name is %s', object_name)
    return object_name
end

function gpu_mgmt:create_resource_obj(bus, device_path)
    log:notice('[device_mgmt]create_resource_obj start, device_path is %s', device_path)
    local object_name = self:get_object_name_by_device_path(bus, device_path)
    if not object_name then
        log:error('[device_mgmt]create_resource_obj failed, because object_name is nil, device_path is %s', device_path)
        return
    end
    -- 创建资源树对象 SystemId默认是1 Id是ObjectName
    local ok, ret, position = pcall(function()
        local gpu_object_name = comm_fun.get_device_name_by_object_name(object_name, comm_defs.GPU_CLASS_NAME)
        local object_position = comm_fun.get_position_by_object_name(gpu_object_name)
        log:notice('[device_mgmt]create_resource_obj, gpu_object_name is %s', gpu_object_name)
        local resource_obj = self.app:CreateGPU(1, gpu_object_name, function(obj)
            obj.ObjectName = gpu_object_name
            obj.ObjectIdentifier = {
                1, "1", "1", object_position
            }
        end)
        return resource_obj, object_position
    end)
    if not ok or not ret then
        log:error('[device_mgmt]create_resource_obj failed, device_path=%s ret=%s', device_path, ret)
        return
    else
        log:notice('[device_mgmt]create_resource_obj success, device_path is %s', device_path)
        return ret, position
    end
end

function gpu_mgmt:on_add_device_obj()
    log:notice('[device_mgmt]on_add_device_obj start')
    comm_fun.set_interface_add_signal(self.bus, self.sig_slot, comm_defs.PCIE_GPU_CARD_DEVICE_PATH_PATTERN,
        comm_defs.PCIE_DEVICE_INTERFACE, function(path)
            self:init_obj(path)
        end)
end

function gpu_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个GPU卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then
            log:notice('[device_mgmt]init_obj already exist, device_path is %s', device_path)
            return 
        end
        local resource_obj, position = self:create_resource_obj(self.bus, device_path)
        if resource_obj then
            self.objects[device_path] = resource_obj
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            -- PCIeGpuCard Gpu Memory 3个设备树对象属性变化
            self:listen_device_obj_property_change(self.bus, self.sig_slot, device_path, resource_obj)
            self:listen_device_obj_property_change(self.bus, self.sig_slot, device_path .. comm_defs.GPU_SUFFIX,
                resource_obj)
            self:listen_device_obj_property_change(self.bus, self.sig_slot, device_path .. comm_defs.MEMORY_SUFFIX,
                resource_obj)
            -- 调用组件回调
            gpu_service.get_instance():on_add_object("GPU", resource_obj, position)
            cmn.skynet.sleep(100)
            -- 再将设备树对象属性赋值给对应资源树对象。需要先调用组件回调，否则会属性同步不成功。
            self:synchronize_property(self.bus, device_path, resource_obj)
            self:synchronize_property(self.bus, device_path .. comm_defs.GPU_SUFFIX, resource_obj)
            self:synchronize_property(self.bus, device_path .. comm_defs.MEMORY_SUFFIX, resource_obj)
        end
    end)
end

function gpu_mgmt:on_del_device_obj()
    log:notice('[device_mgmt]on_del_device_obj start')
    comm_fun.set_interface_del_signal(self.bus, self.sig_slot, comm_defs.PCIE_GPU_CARD_DEVICE_PATH_PATTERN,
        comm_defs.PCIE_DEVICE_INTERFACE, function(path)
            self:del_obj(path)
        end)
end

function gpu_mgmt:del_obj(device_path)
    pcall(function()
        if not self.objects[device_path] then 
            log:notice('[device_mgmt]del_obj not exist, device_path is %s', device_path)
            return 
        end

        local resource_obj = self.objects[device_path]
        local position = resource_obj.ObjectIdentifier[comm_defs.POSITION_INDEX]
        local object_name = resource_obj.ObjectName
        log:notice('[device_mgmt]del_obj class_name is %s, object_name is %s', comm_defs.GPU_CLASS_NAME,
            object_name)
        -- 删除资源树对象
        clsmgmt(comm_defs.GPU_CLASS_NAME):remove(resource_obj)
        -- 调用组件回调
        gpu_service.get_instance():on_delete_object(comm_defs.GPU_CLASS_NAME, resource_obj, position)
        self.objects[device_path] = nil
    end)
end

function gpu_mgmt:ctor(app)
    self.app = app
    self.bus = app.bus
    self.objects = {}  -- GPU卡设备树对象和资源树对象的映射关系
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function gpu_mgmt:init()
    log:notice('[device_mgmt]gpu_mgmt init')
    local ok, ret = pcall(function()
        -- 先监听设备树对象新增信号
        self:on_add_device_obj()
        self:on_del_device_obj()
        -- 获取所有的GPU卡设备树对象，创建对应的资源树对象
        local device_paths = comm_fun.get_all_device_paths(self.bus, comm_defs.PCIE_GPU_CARD_DEVICE_PATH_PATTERN, 1,
            comm_defs.PCIE_DEVICE_INTERFACE)
        for _, device_path in pairs(device_paths) do
            log:notice('[device_mgmt]get device_path: %s', device_path)
            self:init_obj(device_path)
        end
    end)
    if not ok then
        log:error('[device_mgmt]gpu_mgmt init err=%s', ret)
    end
end

return singleton(gpu_mgmt)
