-- 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 log = require 'mc.logging'
local cjson = require 'cjson'
local def = require 'biz_topo.def'
local event_mgmt = require 'infrastructure.event'

local RET_OK<const> = 0
local RET_ERR<const> = 1

local jbog_device_mgmt = {}
jbog_device_mgmt.event_mapping = {
    ['0x08000001'] = 'PCIeCard.PCIeCardUncorrectableErr',
    ['0x08000065'] = 'PCIeCard.PCIeCardCEHardFailure',
    ['0x080000F3'] = 'PCIeCard.PCIeCardLinkSpeedReduced',
    ['0x0800006F'] = 'PCIeCard.PCIeCardBandWidthDecreased'
}

-- led故障码显示三位，为qnn，后两位为槽位信息；switch设备显示qff
-- 若当槽位小于10时，若为1，led故障码显示q01
-- 若当前槽位大于等于10，若为11，led故障码显示为q11
function jbog_device_mgmt.get_pcie_led_param(slot)
    local SLOT_MAX<const> = 31
    local led = 'q'
    if slot <= 9 then
        led = led .. '0' .. slot
    elseif slot > SLOT_MAX then
        led = led .. 'FF'
    else
        led = led .. slot
    end

    return led
end

jbog_device_mgmt.DEFAULT_RESERVERD1 = 0x00
jbog_device_mgmt.DEFAULT_SLOT = 0x00
jbog_device_mgmt.DEFAULT = 0xff
jbog_device_mgmt.BOX_TYPE = 'XPUBox'

-- JBOG switch_id常量，用于分辨是第几个switch
SWITCH_ID_MAP = {
    [0x0c] = 0, -- 1100对应switch0
    [0x0d] = 1, -- 1101对应switch1
    [0x0e] = 2, -- 1110对应switch2
    [0x0f] = 3  -- 1111对应switch3
}

-- JBOG各个标识的掩码
local PHYSICAL_SLOT_NUMBER_BOX_ID<const> = 0x10
local PHYSICAL_SLOT_NUMBER_SWITCH_ID<const> = 0x0f
local PHYSICAL_SLOT_NUMBER_Y<const> = 0x1f
local PHYSICAL_SLOT_NUMBER_X<const> = 0xe0

-- 获取JBOG设备的slot number
function jbog_device_mgmt.get_jbog_slot(slot, resv1, dev_bdf, root_bdf)
    local device_info = {}
    device_info.bus = dev_bdf.bus
    device_info.device = dev_bdf.device
    device_info.func = dev_bdf.func
    device_info.type_id = jbog_device_mgmt.DEFAULT
    device_info.slot_number = jbog_device_mgmt.DEFAULT
    device_info.root_bus = root_bdf.bus
    device_info.root_device = root_bdf.device
    device_info.root_func = root_bdf.func
    if slot ~= jbog_device_mgmt.DEFAULT then
        device_info.slot_number = slot & PHYSICAL_SLOT_NUMBER_Y
        device_info.type_id = (slot & PHYSICAL_SLOT_NUMBER_X) >> 5
    end

    device_info.box_id = (resv1 & PHYSICAL_SLOT_NUMBER_BOX_ID) >> 4
    device_info.switch_id = SWITCH_ID_MAP[resv1 & PHYSICAL_SLOT_NUMBER_SWITCH_ID] or jbog_device_mgmt.DEFAULT
    return device_info
end

-- 判断rootBDF对应的是否JBOG设备，如果是的话返回device_info，否则返回nil
-- 在触发JBOG的场景，触发的是DevBDF
function jbog_device_mgmt.get_physical_slot_number_info(sys_id, segment, bus, device, func, local_db)
    local tbl = local_db.SystemsPCIeTopoInfo
    local db_handle = local_db
    local dev_topo_info = db_handle:select(tbl):where({
        GlobalSystemId = sys_id,
        LocalSystemId = 0,
        Segment = segment,
        Bus = bus,
        Device = device,
        Function = func,
        SecBus = 0,
        SubBus = 0
    }):first()

    -- 数据库损坏或告警bdf为rootBDF的情况，尝试使用上树设备方式告警
    if not dev_topo_info then
        log:info("[JBOGDeviceMgmt] Can't match device info by jbog devBDF %02x:%02x.%x.", bus, device, func)
        return nil
    end

    local root_topo_info = db_handle:select(tbl):where({
        GlobalSystemId = sys_id,
        LocalSystemId = 0,
        Segment = segment,
        SecBus = bus,
        SubBus = bus
    }):first()

    -- 非JBOG设备的情况，继续使用上树设备方式告警
    if not root_topo_info or root_topo_info.ReserReserved1 == jbog_device_mgmt.DEFAULT_RESERVERD1 then
        return nil
    end

    return jbog_device_mgmt.get_jbog_slot(
        root_topo_info.Slot,
        root_topo_info.Reserved1,
        {
            bus = dev_topo_info.Bus,
            device = dev_topo_info.Device,
            func = dev_topo_info.Function
        },
        {
            bus = root_topo_info.Bus,
            device = root_topo_info.Device,
            func = root_topo_info.Function
        }
    )
end


-- 判断当前有没有相同告警，有的话不告警
function jbog_device_mgmt.find_alarm_list(event_code, ...)
    local ok, alarm_list = pcall(event_mgmt.get_latest_alarm_list)
    if not ok then
        log:notice('[JBOGDeviceMgmt] Get_latest_alarm_list failed, ok: %s, alarm_list: %s', ok, alarm_list)
        return false
    end
    -- 没有告警的情况
    if not alarm_list then
        return true
    end
    -- 告警码和软件告警参数列表都一致，即可认为是同一个告警对象
    for _, alarm in pairs(alarm_list) do
        if alarm.EventCode == event_code and alarm.MessageArgs == cjson.encode({...}) then
            -- 当前已有相同告警
            return false
        end
    end
    return true
end

function jbog_device_mgmt.jbog_generate_ras_error_code(state, event_key_id, args, led)
    local param = {}
    param[#param + 1] = { 'ComponentName', jbog_device_mgmt.BOX_TYPE }
    param[#param + 1] = { 'State', state and 'true' or 'false' }
    param[#param + 1] = { 'EventKeyId', event_key_id }
    param[#param + 1] = { 'MessageArgs', cjson.encode(args) }
    param[#param + 1] = { 'SystemId', '' }
    param[#param + 1] = { 'ManagerId', ''}
    param[#param + 1] = { 'ChassisId', '1' }
    param[#param + 1] = { 'NodeId', '1' }
    param[#param + 1] = { 'SubjectType', '18' }    -- Component_XPUBox的Type
    param[#param + 1] = { 'LedFaultCode', led }  -- led 故障码

    local ok, ret = pcall(event_mgmt.generate_event, param)
    if ok then
        log:debug('[JBOGDeviceMgmt] Add jbog event successfully.')
        return RET_OK
    else
        log:error('[JBOGDeviceMgmt] Add jbog event failed, %s', ret)
        return RET_ERR
    end
end

jbog_device_mgmt.JBOG_CARD_ALARM = {
    UCEByBIOS = '0x08000001', -- UCE告警
    PredictiveFault = '0x08000065', -- 预故障告警
    LinkSpeedReduced = '0x080000F3', -- 降速率告警
    BandWidthReduction = '0x0800006F'  -- 降带宽告警
}

-- 0x08000065：%1 %2 [%3] 健康状态降级。
-- 参数预期： JBOG PCIe Card(BOXID:box_id SWID:switch_id SLOT:IOtype_id-slot_number BDF:X:Y.Z)
function jbog_device_mgmt.set_jbog_predictive_fault(device_info, event_val)
    local slot_param = 'JBOG PCIe Card(BOXID:' .. device_info.box_id .. ' SWID:' ..
        device_info.switch_id .. ' SLOT:IO' .. device_info.type_id .. '-' .. device_info.slot_number .. ' BDF:' ..
        string.format("%X", device_info.bus) .. ':' .. string.format("%X", device_info.device) ..'.' ..
        string.format("%X", device_info.func) ..')'
    local param = {'', slot_param, ''}
    if jbog_device_mgmt.find_alarm_list('0x08000065', '', slot_param, '') then
        return jbog_device_mgmt.jbog_generate_ras_error_code(event_val,
            jbog_device_mgmt.event_mapping['0x08000065'], param, '')
    end
    return RET_ERR
end

-- 0x08000001：告警描述：%1 %2 %3 触发了不可纠正错误。故障码：%4
-- 参数预期： PCIe Card(BOXID:box_id SWID:switch_id SLOT:IOtype_id-slot_number BDF:X:Y.Z)
function jbog_device_mgmt.set_jbog_diagnostic_fault(device_info, event_val)
    local slot_param = 'PCIe Card(BOXID:' .. device_info.box_id .. ' SWID:' .. device_info.switch_id ..
        ' SLOT:IO' .. device_info.type_id .. '-' .. device_info.slot_number .. ' BDF:' ..
        string.format("%X", device_info.bus) .. ':' .. string.format("%X", device_info.device) ..'.' ..
        string.format("%X", device_info.func) ..')'
    local param = {'JBOG', slot_param, '', 'NA'}
    if jbog_device_mgmt.find_alarm_list('0x08000001', 'JBOG', slot_param, '', 'NA') then
        return jbog_device_mgmt.jbog_generate_ras_error_code(event_val,
            jbog_device_mgmt.event_mapping['0x08000001'],
            param, jbog_device_mgmt.get_pcie_led_param(device_info.slot_number))
    end
    return RET_ERR
end

-- 0x0800006F：告警描述：%1 PCIe卡%2 %3 带宽降低。
-- 参数预期： BOXID:box_id SWID:switch_id SLOT:IOtype_id-slot_number BDF:X:Y.Z
function jbog_device_mgmt.set_jbog_bandwidth_reduction(device_info, event_val)
    local slot_param = 'BOXID:' .. device_info.box_id .. ' SWID:' .. device_info.switch_id .. ' SLOT:IO' ..
        device_info.type_id .. '-' .. device_info.slot_number .. ' BDF:' ..
        string.format("%X", device_info.bus) .. ':' .. string.format("%X", device_info.device) ..'.' ..
        string.format("%X", device_info.func)
    local param = {'JBOG', slot_param, ''}
    if jbog_device_mgmt.find_alarm_list('0x0800006F', 'JBOG', slot_param, '') then
        return jbog_device_mgmt.jbog_generate_ras_error_code(event_val,
            jbog_device_mgmt.event_mapping['0x0800006F'], param, '')
    end
    return RET_ERR
end

-- 0x080000F3：告警描述：[%1] PCIe卡%2 [%3] 速率降低。
-- 参数预期： BOXID:box_id SWID:switch_id SLOT:IOtype_id-slot_number BDF:X:Y.Z
function jbog_device_mgmt.set_jbog_link_speed_reduced(device_info, event_val)
    local slot_param = 'BOXID:' .. device_info.box_id .. ' SWID:' .. device_info.switch_id ..
        ' SLOT:IO' .. device_info.type_id .. '-' .. device_info.slot_number .. ' BDF:' ..
        string.format("%X", device_info.bus) .. ':' .. string.format("%X", device_info.device) ..'.' ..
        string.format("%X", device_info.func)
    local param = {'JBOG', slot_param, ''}
    if jbog_device_mgmt.find_alarm_list('0x080000F3', 'JBOG', slot_param, '') then
        return jbog_device_mgmt.jbog_generate_ras_error_code(event_val,
            jbog_device_mgmt.event_mapping['0x080000F3'] , param, '')
    end
    return RET_ERR
end

jbog_device_mgmt.set_jbog_fault_func = {
    ['0x08000001'] = jbog_device_mgmt.set_jbog_diagnostic_fault,
    ['0x08000065'] = jbog_device_mgmt.set_jbog_predictive_fault,
    ['0x080000F3'] = jbog_device_mgmt.set_jbog_link_speed_reduced,
    ['0x0800006F'] = jbog_device_mgmt.set_jbog_bandwidth_reduction
}

-- 设置jbog故障（包含触发和恢复告警）
-- device_info：要触发的设备信息，由get_jbog_slot返回
-- event_val: 告警值，由rpc调用方提供
-- jbog_card_alarm：告警类型，对应rpc命令
function jbog_device_mgmt.set_jbog_fault(device_info, event_val, jbog_card_alarm)
    local func = jbog_device_mgmt.set_jbog_fault_func[jbog_card_alarm]
    local ok, ret = pcall(func, device_info, event_val)
    if ok then
        return ret
    else
        return RET_ERR
    end
end

function jbog_device_mgmt:cancel_soft_alarm(system_id)
    local ok, alarm_list = pcall(event_mgmt.get_latest_alarm_list)
    if not ok or not alarm_list then
        return
    end
    for _, alarm in pairs(alarm_list) do
        if alarm.ComponentName == jbog_device_mgmt.BOX_TYPE then
            jbog_device_mgmt.jbog_generate_ras_error_code(false, jbog_device_mgmt.event_mapping[alarm.EventCode],
            cjson.decode(alarm.MessageArgs), '')
        end
    end
end

function jbog_device_mgmt:power_off_clear_alarm(system_id, power_status)
    if power_status == 'OFF' then
        jbog_device_mgmt:cancel_soft_alarm(system_id)
    end
end

return jbog_device_mgmt