-- 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 ipmi = require 'ipmi'
local bs = require 'mc.bitstring'
local log = require 'mc.logging'
local enums = require 'ipmi.enums'

local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc

local imu_cmd = {}

local pci_info_address_list = {
    VID_DID_INFO_ADDR = 0x00, -- PCI配置空间中的VID/DID信息的起始地址
    SUBVID_SUBDID_INFO_ADDR = 0X2C, -- PCI配置空间中的SubVID/SubDID信息的起始地址
    CLASS_CODE_INFO_ADDR = 0X08 -- PCI配置空间中的ClassCode信息的起始地址0X09，需要4字节对齐获取ECAM空间是按照32bit获取。
}
local UINT8_MAX<const> = 0xff

-- 通过系统编号找对应的imu命令通道
local ipmi_channel_config = {
    [1] = 0 -- 默认的ipmi通道是0
}

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>>')
-- 解析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

-- 发送命令向PMU查询PCI配置信息
function imu_cmd.get_info_from_pmu(sys_id, bus, pcie_info)
    if pcie_info == nil then
        log:error('pcie_info is nil')
        return nil
    end
    if pcie_info and pcie_info.bus_num == UINT8_MAX then
        log:debug('get_info_from_pmu failed. bus_num is 0xff.')
        return nil
    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 instance = imu_cmd.ipmi_channel_config[sys_id] or 0
    local result, payload
    for _ = 1, 3 do
        result, payload = ipmi.request(bus, {channel_type.CT_ME:value(), instance},
            {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
    end
    log:error('get pci register info failed, error: %s', result)
    return nil
end

local get_pmu_version_rsp = bs.new('<<Device1, Device2, PmuVersion1, PmuVersion2>>')
-- 发送ipmi命令,查询pmu状态和pmu的版本信息
function imu_cmd.get_pmu_status(bus)
    -- 向pmu发送 ipmi消息，根据发送是否成功来判断 pmu状态
    local cc, resp_data = ipmi.request(bus, channel_type.CT_ME:value(),
        {DestNetFn = 0x06, Cmd = 0x01, Payload = nil})
    if cc ~= comp_code.Success then
        log:debug('ipmi send failed, error: %s', cc)
        return cc, nil
    end
    local res = get_pmu_version_rsp:unpack(resp_data)
    return cc, res
end

imu_cmd.pci_info_address_list = pci_info_address_list
imu_cmd.parse_info_from_pmu = parse_info_from_pmu
imu_cmd.ipmi_channel_config = ipmi_channel_config

return imu_cmd
