-- 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.

-- Description: PCIe带外管理，SmBus协议入口
local log = require 'mc.logging'
local st = require 'device.lib_pcie_oob_mgmt.smbus.struct'
local def = require 'device.lib_pcie_oob_mgmt.def'
local crc32 = require 'device.lib_pcie_oob_mgmt.crc32'

local M = {}

local HEADER_LEN <const> = 12
local CRC32_LEN <const> = 4

local e_error_code <const> = {
    SUCCESS = 0,
    OPCODE_NOT_SUPPORT = 1,
    PARAMETER_ERROR = 2,
    INTERNAL_ERROR = 3,
    CRC_ERROE = 4
}

local datalen_table <const> = {
    [def.e_opcode.CAPABILITY] = 0xffffffff,
    [def.e_opcode.GET_FIRMWARE_VERSION] = 0x13
}

local function get_max_frame_len(opcode, frame_len_config)
    local max_frame_len = datalen_table[opcode]
    if datalen_table[opcode] >= frame_len_config then
        max_frame_len = frame_len_config
    end
    if max_frame_len < HEADER_LEN + CRC32_LEN then
        log:error('Max frame length error.')
        return false
    end
    return true, max_frame_len
end

-- 构造命令请求
local function constuct_request(opcode, offset, read_len)
    local request = st.request.new()
    request.command = opcode
    request.offset = offset
    request.read_length = read_len
    local req_bin_temp = st.request.pack(request)
    request.crc = crc32(req_bin_temp:sub(1, HEADER_LEN), 0, true)

    local req_bin = st.request.pack(request)
    return req_bin, st.request.len
end

-- 对芯片进行读写，发送请求并接收响应
---@param io ChipIO
local function send_and_receive(io, req_bin, max_frame_len, delay_time)
    -- 发送
    local ok = io:write(0, req_bin)
    if not ok then
        log:debug('Smbus write failed.')
        return false
    end

    -- 延时
    io:delay(delay_time)

    -- 接收
    local ok, resp_bin = io:read(0, max_frame_len)
    if not ok then
        log:debug('Smbus read failed.')
        return false
    end

    -- crc校验
    local bin_data = string.sub(resp_bin, 1, max_frame_len - CRC32_LEN)
    local bin_crc = string.sub(resp_bin, max_frame_len - CRC32_LEN + 1, max_frame_len)
    local crc_cal = crc32(bin_data, 0, true)
    local crc_expect = string.unpack('I4', bin_crc)
    if crc_cal ~= crc_expect then
        log:debug('Crc check error.')
        return false
    end

    return true, resp_bin
end

local function check_error_code(error_code)
    local ret = false
    local switch = {
        [e_error_code.SUCCESS] = function ()
            ret = true
        end,
        [e_error_code.OPCODE_NOT_SUPPORT] = function ()
            log:error('Opcode not support.')
        end,
        [e_error_code.PARAMETER_ERROR] = function ()
            log:error('Parameter error.')
        end,
        [e_error_code.INTERNAL_ERROR] = function ()
            log:error('Internal error.')
        end,
        [e_error_code.CRC_ERROE] = function ()
            log:error('CRC error.')
        end
    }

    if switch[error_code] then
        switch[error_code]()
    else
        log:error('Analysis error code(%d) failed.', error_code)
    end
    return ret
end

local function process_error()
    return false
end

-- 处理响应，将多帧数据汇总
---@param total_resp table @多帧数据汇总
---@param total_data_len number @多帧数据总长
---@param resp table @当前帧响应
---@return boolean,boolean @ok/err, 数据是否全部接收完成
local function process_resp(total_resp, total_data_len, resp)
    local start_offset, end_offset
    local is_completed = false
    -- 第一帧，保存数据头部分
    if total_resp.data == '' then
        total_resp.error_code = resp.error_code
        total_resp.opcode = resp.opcode
        total_resp.total_length = resp.total_length
        total_resp.data_length = resp.data_length
    end

    start_offset = 1
    -- 最后一帧，仅保存有效数据部分
    if total_data_len + resp.data_length >= resp.total_length then
        end_offset = resp.total_length - total_data_len
        is_completed = true
    else
        end_offset = resp.data_length
    end
    total_resp.data = total_resp.data .. resp.data:sub(start_offset, end_offset)

    return true, is_completed
end

local function send_command(opcode, io, delay_time, frame_len_config)
    local ok, max_frame_len = get_max_frame_len(opcode, frame_len_config)
    if not ok then
        return false
    end
    local data_len = max_frame_len - HEADER_LEN - CRC32_LEN
    local total_resp = st.response.new()
    total_resp.data = ''
    local total_data_len = 0
    local req_bin, resp_bin, is_completed, resp
    repeat
        req_bin, _ = constuct_request(opcode, total_data_len, data_len)
        ok, resp_bin = send_and_receive(io, req_bin, max_frame_len, delay_time)
        if not ok then
            goto error_exit
        end
        resp = st.response.unpack(resp_bin)
        ok, is_completed = process_resp(total_resp, total_data_len, resp)
        if not ok then
            goto error_exit
        end
        total_data_len = total_data_len + resp.data_length
    until is_completed

    ok = check_error_code(total_resp.error_code)
    if ok then
        return true, total_resp.data, total_resp.total_length
    end

    ::error_exit::
    return process_error()
end

-- 对响应体进行处理，处理函数可以为空
local post_process_func = {
    [def.e_opcode.CAPABILITY] = nil,
    [def.e_opcode.GET_FIRMWARE_VERSION] = nil
}

-- 信息获取统一入口函数
function M.get_info(opcode, io, delay_time, frame_len_config)
    -- 发送命令
    local ok, data, data_len = send_command(opcode, io, delay_time, frame_len_config)
    if not ok then
        return false
    end

    -- 获取响应结构体
    local struct = st.get_struct(opcode)
    if data_len < struct.len then
        return false, def.e_err_code.REQUEST_LEN_ERROR
    end
    -- 解包响应数据
    local resp = struct.unpack(data)
    -- 后处理
    if post_process_func[opcode] then
        post_process_func[opcode](resp)
    end

    return true, resp
end

-- test
M.constuct_request = constuct_request
M.process_resp = process_resp
M.check_error_code = check_error_code
return M