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

local GET_ARM_NPU_CMD<const> = 0x17
local DMP_LAST_REQ<const> = 0x80
local channel_type = enums.ChannelType
local comp_code = ipmi.types.Cc

local imu_cmd = {}

-- pci配置空间定义，定义了从0x00-0x40所有字的起始地址，具体定义见/docs/pic/PCIeHeader.png
-- 变量命名以HeaderType = 0设备为准
local pci_info_address_list = {
    VID_DID_INFO_ADDR = 0x00, -- PCI配置空间中的VID/DID信息的起始地址
    COMMAND_STATUS_INFO_ADDR = 0x04,
    CLASS_CODE_INFO_ADDR = 0x08, -- PCI配置空间中的ClassCode信息的起始地址0X09，需要4字节对齐获取ECAM空间是按照32bit获取。
    HEAD_TYPE_INFO_ADDR = 0x0C,
    BASE_ADDR_0_ADDR = 0x10,
    BASE_ADDR_1_ADDR = 0x14,
    BASE_ADDR_2_ADDR = 0x18,
    BASE_ADDR_3_ADDR = 0x1C,
    BASE_ADDR_4_ADDR = 0x20,
    BASE_ADDR_5_ADDR = 0x24,
    CARDBUS_CIS_POINTER_ADDR = 0x28,
    SUBVID_SUBDID_INFO_ADDR = 0x2C, -- PCI配置空间中的SubVID/SubDID信息的起始地址
    EXPANSION_ROM_BASE_ADDR = 0x30,
    RESERVED_1_ADDR = 0X34,
    RESERVED_2_ADDR = 0X38,
    INTERRUPT_LINE_ADDR = 0x3C
}

-- 定义特定字段地址中每个字节的定义
local addr_detail = {
    class_code_info_addr = {
        CLASS_CODE = 4
    },
    base_addr_2_addr = {
        SEC_BUS = 2,
        SUB_BUS = 3
    }
}

-- pci配置空间BaseClassCode定义，对应ClassCode的高8位
local pci_info_base_class_code = {
    CLASS_CODE_NET_CARD = 0x02,
    CLASS_CODE_BRIDGE = 0x06
}

-- BDF的范围
local bdf_range = {
    MIN_DEVICE = 0,
    MAX_DEVICE = 31,
    MIN_FUNCTION = 0,
    MAX_FUNCTION = 7
}
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('BDF=%02x:%02x.%x get pci register info successfully',
                pcie_info.bus_num, pcie_info.device_num, pcie_info.function_num)
            return info_arr
        end
    end
    log:info('BDF=%02x:%02x.%x get pci register info failed, error: %s',
        pcie_info.bus_num, pcie_info.device_num, pcie_info.function_num, 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

local pcie_info_req = bs.new([[<<
    0xDB0700:3/unit:8,
    cmd:1/unit:8,
    lun:1/unit:8,
    para:1/unit:8,
    op_cmd:1/unit:8,
    op_fun:1/unit:8,
    offset:4/unit:8,
    data_length:4/unit:8
>>]])
local pcie_info_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    respond_length:4/little-unit:8,
    pcie_lanes:4/unit:8,
    pcie_rates:4/unit:8,
    dev_bus:4/unit:8,
    dev_device:4/unit:8,
    dev_function:4/unit:8
>>]])

function imu_cmd.get_npu_pcie_info(bus, id)
    local pcie_info = {
        pcie_lanes = 0,
        pcie_rates = 0, 
        dev_bus = 0,
        dev_device = 0,
        dev_function = 0
    }
    local req_data = pcie_info_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0xB8,
        op_fun = 0x06,
        offset = 0,
        data_length = 20
    })
    local result, payload = ipmi.request(bus, {channel_type.CT_ME:value(), id}, {
        DestNetFn = 0x30,
        Cmd = 0x98,
        payload = req_data
    })
    if result == comp_code.Success then
        local rsp = pcie_info_rsp:unpack(payload)
        pcie_info.pcie_lanes = rsp.pcie_lanes
        pcie_info.pcie_rates = rsp.pcie_rates
        pcie_info.dev_bus = rsp.dev_bus
        pcie_info.dev_device = rsp.dev_device
        pcie_info.dev_function = rsp.dev_function
    end
    return result, pcie_info
end

local tuple_info_rsp = bs.new([[<<
    head:3/unit:8,
    total_length:4/little-unit:8,
    respond_length:4/little-unit:8,
    vendor_id:4/unit:8,
    device_id:4/unit:8,
    sub_vendor_id:4/unit:8,
    sub_device_id:4/unit:8
>>]])

function imu_cmd.get_npu_pcie_sub_vid_did_info(bus, id)
    local pcie_info = {
        vendor_id = 0,
        device_id = 0, 
        sub_vendor_id = 0,
        sub_device_id = 0
    }
    local req_data = pcie_info_req:pack({
        cmd = GET_ARM_NPU_CMD,
        lun = DMP_LAST_REQ,
        para = 0,
        op_cmd = 0xB8,
        op_fun = 0x06,
        offset = 20,
        data_length = 16
    })
    local result, payload = ipmi.request(bus, {channel_type.CT_ME:value(), id}, {
        DestNetFn = 0x30,
        Cmd = 0x98,
        payload = req_data
    })
    if result == comp_code.Success then
        local rsp = tuple_info_rsp:unpack(payload)
        pcie_info.vendor_id = rsp.vendor_id
        pcie_info.device_id = rsp.device_id
        pcie_info.sub_vendor_id = rsp.sub_vendor_id
        pcie_info.sub_device_id = rsp.sub_device_id
    end
    return result, pcie_info
end

imu_cmd.pci_info_address_list = pci_info_address_list
imu_cmd.addr_detail = addr_detail
imu_cmd.pci_info_base_class_code = pci_info_base_class_code
imu_cmd.bdf_range = bdf_range
imu_cmd.parse_info_from_pmu = parse_info_from_pmu
imu_cmd.ipmi_channel_config = ipmi_channel_config

return imu_cmd
