-- 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 class = require 'mc.class'
local log = require 'mc.logging'
local cmn = require 'common'
local ctx = require 'mc.context'
local comm = require 'device.lib_pcie_oob_mgmt'
local def = require 'device.lib_pcie_oob_mgmt.def'
local ipmi = require 'ipmi'
local enums = require 'ipmi.enums'
local bs = require 'mc.bitstring'
local mdb = require 'mc.mdb'
local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc

local c_pcie_card = class()

local RET_OK <const> = 0
local RET_ERR <const> = 1
local CAP_ID_INDEX <const> = 1
local ADDR_INDEX <const> = 2
local PCI_CAP_ID_EXP <const> = 0x10
local PCIE_LINK_CAP_REG_OFFSET <const> = 0x0C -- pcie link capabilities寄存器偏移
local PCIE_LINK_CONTROL_OFFSET <const> = 0x10 -- pcie link control寄存器偏移
local PCIE_LINK_WIDTH_OFFSET <const> = 4
local PCIE_LINK_WIDTH_MASK <const> = 0x3F
local PCIE_CAP_ID_OFFSET <const> = 0x34
local DEFAULT_INTERFACE <const> = 'bmc.kepler.Systems.PCIeDevices.PCIeCard'

local support_protocol <const> = {def.e_protocol.SMBUS}

-- 带外管理信息获取
function c_pcie_card:pcie_oob_mgmt_update_info()
    -- 判断协议是否支持
    if not cmn.find(support_protocol, self.chip_info.protocol) then
        return false
    end

    local ok = self:pcie_oob_mgmt_init()
    if not ok then
        log:error('PCIe card oob management init failed.')
        return false
    end

    local ok, firmware_version = comm.get_info(self.chip_info, def.e_opcode.GET_FIRMWARE_VERSION)
    if ok then
        self:set_prop('FirmwareVersion', firmware_version)
    end
end

function c_pcie_card:pcie_oob_mgmt_init()
    local ok, opcodes = comm.get_info(self.chip_info, def.e_opcode.CAPABILITY)
    if not ok then
        return false
    end
    self.chip_info.opcodes = opcodes
    return true
end

-- FunctionClass 1是Raid
function c_pcie_card:is_raid()
    return self:get_prop('FunctionClass') == 1
end

function c_pcie_card:get_prop(name, interface)
    if not self.mds then
        return nil
    end

    if interface then
        return self.mds[interface][name]
    elseif self.mds[name] then
        return self.mds[name]
    else
        return self.mds[DEFAULT_INTERFACE][name]
    end
end


function c_pcie_card:set_prop(name, val, interface)
    if not self.mds then
        return nil
    end

    if interface then
        self.mds[interface][name] = val
    elseif self.mds[name] then
        self.mds[name] = val
    else
        self.mds[DEFAULT_INTERFACE][name] = val
    end
end

local get_info_from_pmu_req = bs.new('<<0xDB0700:3/unit:8, cpu_num,' ..
                                     'pci_addr_1:1/unit:8,' ..
                                     'pci_addr_2:1/unit:8,' ..
                                     'pci_addr_3:1/unit:8,' ..
                                     'pci_addr_4:1/unit:8,' ..
                                     'read_length>>')
local get_info_from_pmu_rsp = bs.new('<<0xDB0700:3/unit:8, info_byte:1/unit:8, tail/binary>>')
local get_info_per_byte = bs.new('<<info_byte:1/unit:8, tail/binary>>')

local links_table <const> = {
    [1] = 'X1', --PCIe卡带宽 X1
    [2] = 'X2', --PCIe卡带宽 X2
    [4] = 'X4', --PCIe卡带宽 X4
    [8] = 'X8', --PCIe卡带宽 X8
    [12] = 'X12', --PCIe卡带宽 X12
    [16] = 'X16' --PCIe卡带宽 X16
}

local LINKS_SPEED_TABLE <const> = {
    [1] = '2.5GT/s', --GEN1
    [2] = '5.0GT/s', --GEN2
    [3] = '8.0GT/s', --GEN3
    [4] = '16.0GT/s', --GEN4
    [5] = '32.0GT/s',--GEN5
    [6] = 'RsvdP',--RsvdP
    [7] = 'RsvdP',--RsvdP
}

local function link_speed_to_str(link_speed)
    if LINKS_SPEED_TABLE[link_speed] then
        return LINKS_SPEED_TABLE[link_speed]
    end
    log:error("link_speed_to_str:faild to link speed to string(%d).", link_speed)
    return 'N/A'
end

local function link_width_to_str(link_width)
    if links_table[link_width] then
        return links_table[link_width]
    end
    log:error("link_width_to_str:faild to link width to string(%d).", link_width)
    return 'N/A'
end

-- 解析ipmi返回消息中的PCI信息
local function parse_info_from_pmu(payload, length)
    local rsp = get_info_from_pmu_rsp:unpack(payload)
    local info_byte = rsp.info_byte
    local tail = rsp.tail
    local i = 1
    local result_arr = {}
    local read_loop
    result_arr[#result_arr + 1] = info_byte
    -- 根据指定长度，挨个读取ipmi返回数据中的字节
    while i < length and tail ~= nil and #tail ~= 0 do
        read_loop = get_info_per_byte:unpack(tail)
        info_byte, tail = read_loop.info_byte, read_loop.tail
        result_arr[#result_arr + 1] = info_byte
        i = i + 1
    end
    return result_arr
end

local function get_pcie_info_from_ipmi(bus, pcie_info)
    if pcie_info == nil then
        log:error('pcie_info is nil')
        return
    end
    local cpu_id = 0
    if pcie_info.is_local == 1 then
        cpu_id = (pcie_info.cpu_id & 0x07) | 0x40
    else
        cpu_id = pcie_info.cpu_id & 0x07
    end
    local addr = {}
    addr[1] = pcie_info.address & 0xff
    addr[2] = ((pcie_info.function_num & 0x07) << 4) | ((pcie_info.device_num & 0x01) << 7)
    addr[3] = ((pcie_info.device_num >> 1) & 0x0f) | ((pcie_info.bus_num & 0x0f) << 4)
    addr[4] = pcie_info.bus_num >> 4
    local length = pcie_info.read_length == 4 and 3 or 4
    local req_data = get_info_from_pmu_req:pack({cpu_num = cpu_id, pci_addr_1 = addr[1], pci_addr_2 = addr[2],
                                                pci_addr_3 = addr[3], pci_addr_4 = addr[4], read_length = length})
    local result, payload = ipmi.request(bus, channel_type.CT_ME:value(), {DestNetFn = 0x2e, Cmd = 0x44, Payload = req_data})
    if result == comp_code.Success then
        local info_arr = parse_info_from_pmu(payload, pcie_info.read_length)
        log:info('get pci register info successfully')
        return info_arr
    end
    log:error('get pci register info failed, error: %s', result)
    return
end

function c_pcie_card:pcie_get_capid_addr(info, cap_id)
    info['address'] = PCIE_CAP_ID_OFFSET
    local reg_value = get_pcie_info_from_ipmi(self.bus, info)
    if not reg_value then
        log:error("pcie_get_capid_addr: failed to get first cap id.")
        return
    end
    local success_flag = false
    info['address'] = reg_value[CAP_ID_INDEX]
    for _ = 1, 20 do -- 进行20次cap id匹配
        reg_value = get_pcie_info_from_ipmi(self.bus, info)
        if not reg_value then
            log:error("pcie_get_capid_addr: failed to get reg address.")
            return
        end

        if reg_value[CAP_ID_INDEX] ~= cap_id then
            if not reg_value[ADDR_INDEX] or reg_value[ADDR_INDEX] == 0 then
                log:error("pcie_get_capid_addr: not find cap id(%d).", cap_id)
                return
            end
            info['address'] = reg_value[ADDR_INDEX]
            goto CONTINUE
        end
        success_flag = true
        break
        ::CONTINUE::
    end
    if success_flag then
        return info['address']
    end
    return
end

-- 从PCIeDevice同步属性到PCIeCard
function c_pcie_card:sync_info_to_card(pcie_device_obj)
    local prop_map = {
        ['MultihostPresence'] = function (value)
            local card_for_servers = {}
            local cur_sys_id = 1
            while value > 0 do
                if value & 1 > 0 then
                    table.insert(card_for_servers, cur_sys_id)
                    log:debug('[sync_info_to_card], card_for_servers add system id=%s, position=%s',
                        cur_sys_id, self.position)
                end
                value = value >> 1
                cur_sys_id = cur_sys_id + 1
            end
            self:set_prop('CardForServers', card_for_servers)
        end
    }
    -- 注册属性变化的监听
    pcie_device_obj.mds.property_changed:on(
        function(name, value, sender)
            if prop_map[name] then
                prop_map[name](value)
                return
            end
        end)
    -- 同步一次属性值
    local value
    for device_prop, card_prop in pairs(prop_map) do
        value = pcie_device_obj:get_prop(device_prop)
        log:debug('sync_info_to_card, name=%s, value=%s', device_prop, value)
        local ok, err = pcall(function()
            prop_map[device_prop](value)
        end)
        if not ok then
            log:warn('sync_info_to_card, not ok, err=%s', err)
        end
    end
end

function c_pcie_card:get_pcie_lang_info(pcie_device_obj)
    local pcie_info = {
        cpu_id = pcie_device_obj:get_prop('SocketID'),
        is_local = 0,
        bus_num = pcie_device_obj:get_prop('DevBus'),
        device_num = pcie_device_obj:get_prop('DevDevice'),
        function_num = pcie_device_obj:get_prop('DevFunction'),
        address = 0,
        read_length = 4
    }
    local addr = self:pcie_get_capid_addr(pcie_info, PCI_CAP_ID_EXP)
    if not addr then
        log:error("get_pcie_lang_info: get cap id addr failed.")
        return RET_ERR
    end
    pcie_info['address'] = addr + PCIE_LINK_CAP_REG_OFFSET
    local ret = get_pcie_info_from_ipmi(self.bus, pcie_info)
    if not ret then
        log:error("get_pcie_lang_info: get link cap reg value failed")
        return RET_ERR
    end
    local link_width_ability = ((ret[1] >> PCIE_LINK_WIDTH_OFFSET) + (ret[2] << PCIE_LINK_WIDTH_OFFSET))
        & PCIE_LINK_WIDTH_MASK
    self.mds['LinkWidthAbility'] = link_width_to_str(link_width_ability)

    local link_speed_capability = ret[1] & 0xF
    pcie_info['address'] = addr + PCIE_LINK_CONTROL_OFFSET
    ret = get_pcie_info_from_ipmi(self.bus, pcie_info)
    if not ret then
        log:error("get_pcie_lang_info: get link status reg value failed")
        return RET_ERR
    end
    local link_width = ((ret[3] >> PCIE_LINK_WIDTH_OFFSET) + (ret[4] << PCIE_LINK_WIDTH_OFFSET))
        & PCIE_LINK_WIDTH_MASK
    self.mds['LinkWidth'] = link_width_to_str(link_width)

    local link_speed = ret[3] & 0xF
    local ok, err = pcall(function ()
        self.mds['LinkSpeed'] = link_speed_to_str(link_speed) 
    end)
    if not ok then
        log:debug("[pcie_device] link_speed_to_str link_speed error:%s", err)
    end

    local ok, err = pcall(function ()
        self.mds['LinkSpeedCapability'] = link_speed_to_str(link_speed_capability)
    end)
    if not ok then
        log:debug("[pcie_device] link_speed_to_str link_speed_capability error:%s", err)
    end
    return RET_OK
end

-- 数字转换为PcbVersion的格式
local function pcbid_to_pcbver(pcbid)
    if type(pcbid) ~= 'number' or (pcbid < 1 or pcbid > 26) then -- PcbID的合法取值为1-26
        log:error('pcbid_to_pcbver: The pcbid %s is invalid.', pcbid)
        return nil
    end
    return '.' .. string.char(pcbid + 64) -- 64为字符'A'的前一个字符对应的ASCII码
end

-- 更新pcbversion字段的任务，因为刚加载时会读取不到pcb版本，因此要启动线程做重试处理
function c_pcie_card:task_update_pcb_version()
    if self.fetch_pcb_flag == false then
        return
    end
    cmn.skynet.fork(function ()
        local timeout = 120
        local version = nil
        local pcbid
        repeat
            pcbid = self:get_prop('PcbID')
            if pcbid and pcbid ~= 0 then
                version = pcbid_to_pcbver(pcbid)
                if version then
                    self:set_prop('PcbVersion', version)
                end
            end
            cmn.skynet.sleep(200)
            timeout = timeout - 1
        until version or timeout <= 0
        if version == nil then
            self.fetch_pcb_flag = false
            log:error('Get PCB Version failed')
        end
    end)
end

function c_pcie_card:init()
    self.fetch_pcb_flag = true
    self:task_update_pcb_version()
    -- chip_info初始化
    local COMMON_DELAY_TIME_FOR_RW <const> = 10
    ---@class ChipInfo @芯片信息
    ---@field opcodes number[] @能力码
    ---@field protocol string @协议类型
    ---@field max_frame_len number @单帧长度
    ---@field delay_time number @io读写间隔时间
    ---@field io ChipIO @封装芯片的读写方法
    self.chip_info = {
        opcodes = {def.e_opcode.CAPABILITY},
        protocol = self:get_prop('Protocol'),
        max_frame_len = self:get_prop('MaxFrameLen'),
        delay_time = COMMON_DELAY_TIME_FOR_RW,
        ---@class ChipIO @芯片读写
        io = {
            ref_chip = self:get_prop('RefChip'),
            read = function (obj, offset, len)
                return pcall(function () return obj.ref_chip:Read(ctx.new(), offset, len) end)
            end,
            write = function (obj, offset, data)
                return pcall(function () return obj.ref_chip:Write(ctx.new(), offset, data) end)
            end,
            -- time单位：ms
            delay = function (obj, time)
                cmn.skynet.sleep(time // 10)
            end
        }
    }
end

function c_pcie_card:ctor(mds_obj, position, bus)
    self.mds = mds_obj
    self.position = position
    self.bus = bus
end

return c_pcie_card