-- 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: 眼图测试
local eye_diagrame = {}

local log = require 'mc.logging'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local msg = require 'general_hardware.ipmi.ipmi_message'
local client = require 'general_hardware.client'
local json = require 'cjson'
local context = require 'mc.context'
local cmn = require 'common'
local drv = require 'libsoc_adapter.serdes'

local EYE_DIAGRAME_TYPE_TABLE<const> = {
    [0] = 'PCIe0',
    [1] = 'PCIe1',
    [2] = 'GMAC2',
    [3] = 'GMAC3',
    [4] = 'USB'
}

local EYE_DIAGRAME_OPTIONS_ENUM<const> = {
    START_TEST = 0x0,
    GET_RESULT = 0x1
}

local EYE_DIAGRAME_OPTIONS<const> = {
    [0] = 0x0,
    [1] = 0x1
}

local SCOPE_SEL_TABLE<const> = {
    [0] = 'before AFE sampling',
    [1] = 'after AFE sampling'
}

local EYE_DIAGRAME_RESULT<const> = {
    TEST_WIDTH = 'test_width',
    REAL_WIDTH = 'real_width',
    TEST_HEIGHT = 'test_height',
    REAL_HEIGHT = 'real_height'
}

local EYE_DIAGRAME_DATA_TYPE_TABLE<const> = {
    [0] = EYE_DIAGRAME_RESULT.TEST_WIDTH,
    [1] = EYE_DIAGRAME_RESULT.REAL_WIDTH,
    [2] = EYE_DIAGRAME_RESULT.TEST_HEIGHT,
    [3] = EYE_DIAGRAME_RESULT.REAL_HEIGHT
}

local TASK_STATUS<const> = {
    INIT = 'init',
    STARTED = 'started',
    FINISHED = 'finished'
}

local SCAN_MODE_OPTIONS<const> = {
    [0x00] = "FIXED_POINT",
    [0x01] = "ISI",
    [0x02] = "WIDTH",
    [0x03] = "HEIGHT",
    [0xff] = ""
}

local PRE_TAP_OPTIONS<const> = {
    [0x01] = "PRE_TAP_BIT0",
    [0x02] = "PRE_TAP_BIT1",
    [0x03] = "PRE_TAP_BIT01",
    [0xff] = ""
}

local PATTERN_TYPE_OPTIONS<const> = {
    [0x00] = "PRE_TAP_BIT0",
    [0x01] = "PRE_TAP_BIT1",
    [0xff] = ""
}

local EYE_TYPE_OPTIONS<const> = {
    [0x00] = "EYE_BASIC",
    [0x01] = "EYE_TAP1",
    [0x02] = "EYE_TAP2",
    [0xff] = ""
}
local PATH_TYPE_OPTIONS<const> = {
    [0x01] = "EYE_PATH_Q",
    [0x02] = "EYE_PATH_I",
    [0x04] = "EYE_PATH_QB",
    [0x05] = "EYE_PATH_Q_QB",
    [0x08] = "EYE_PATH_IB",
    [0x0a] = "EYE_PATH_I_IB",
    [0x0f] = "EYE_PATH_COMPOSITE",
    [0xff] = ""
}

local eye_diagram_result = false
local task_status = TASK_STATUS.INIT

local function start_test(type, scope_sel, compare_value)
    if task_status ~= TASK_STATUS.INIT then
        log:notice('[general_hardware]task started, status: %s', task_status)
        return msg.GetEyeDiagramRsp.new(comp_code.Success, 0, '')
    end

    local ret, ok, row, col, obj
    local manager_id = 1
    ok, obj = pcall(client.GetBmcDfxEyeDiagramObject, client, {ManagerId = manager_id})
    if not ok or not obj then
        log:error('rpc call GetEyeDiagram has error: %s', obj)
        return msg.GetEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, '')
    end

    task_status = TASK_STATUS.STARTED
    cmn.skynet.fork(function()
        ok, row, col = obj.pcall:StartTest(context.new(), type, scope_sel)
        log:notice("[general_hardware] start test get eye diagrame, row: %s, col: %s", row, col)
        if not ok then
            log:error('rpc call StartTest has error, error: %s', row)
            task_status = TASK_STATUS.INIT
            return msg.GetEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, '')
        end
        ok, ret = obj.pcall:GetResult(context.new(), row, col, compare_value)
        if not ok then
            task_status = TASK_STATUS.INIT
            log:error('rpc call GetResult has error: %s', ret)
            return msg.GetEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, '')
        end
        eye_diagram_result = ret
        task_status = TASK_STATUS.FINISHED
    end)

    return msg.GetEyeDiagramRsp.new(comp_code.Success, 0, '')
end

local function get_result(data_type)
    if not data_type or not EYE_DIAGRAME_DATA_TYPE_TABLE[data_type] then
        log:error('[general_hardware]DataType[%s] is nil', data_type)
        return msg.GetEyeDiagramRsp.new(comp_code.ParmOutOfRange, 0,'')
    end

    if task_status == TASK_STATUS.INIT or task_status == TASK_STATUS.STARTED then
        log:notice('[general_hardware]cannot get result, status: %s', task_status)
        return msg.GetEyeDiagramRsp.new(comp_code.DataNotAvailable, 0, '')
    end

    local ok, result_json = pcall(function()
        return json.decode(eye_diagram_result)
    end)
    eye_diagram_result = false
    task_status = TASK_STATUS.INIT
    if not ok or not result_json then
        log:error("[general_hardware]eye diagrame json: json format error, result_json: %s", result_json)
        return msg.GetEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, '')
    end

    log:notice("[general_hardware]eye diagrame t_h: %s, r_h: %s, t_w: %s, r_w: %s",
        result_json[EYE_DIAGRAME_RESULT.TEST_HEIGHT], result_json[EYE_DIAGRAME_RESULT.REAL_HEIGHT],
        result_json[EYE_DIAGRAME_RESULT.TEST_WIDTH], result_json[EYE_DIAGRAME_RESULT.REAL_WIDTH])
    local result = tostring(result_json[EYE_DIAGRAME_DATA_TYPE_TABLE[data_type]])
    if not result then
        log:error('[general_hardware]DataType[%s] is nil', data_type)
        return msg.GetEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, '')
    end

    local length = #result
    return msg.GetEyeDiagramRsp.new(comp_code.Success, length, result)
end

function eye_diagrame.get_eye_diagrame_result(req, ctx)
    log:notice("[general_hardware] eye diagrame Option: %s, type: %s, ScopeSel: %s, CompareValue: %s",
        req.Option, req.DeviceType, req.ScopeSel, req.CompareValue)

    local ret = msg.GetEyeDiagramRsp.new(comp_code.ParmOutOfRange, 0, '')
    if not req.Option or not EYE_DIAGRAME_OPTIONS[req.Option] then
        log:error('[general_hardware]Option[%s] is illegal', req.Option)
        return ret
    end

    if not req.ScopeSel or not EYE_DIAGRAME_TYPE_TABLE[req.ScopeSel] then
        log:error('[general_hardware]ScopeSel[%s] is illegal', req.ScopeSel)
        return ret
    end

    if not req.DeviceType or not SCOPE_SEL_TABLE[req.DeviceType] then
        log:error('[general_hardware]Type[%s] is illegal', req.DeviceType)
        return ret
    end

    if req.Option == EYE_DIAGRAME_OPTIONS_ENUM.START_TEST then
        ret = start_test(req.DeviceType, req.ScopeSel, req.CompareValue)
    end

    if req.Option == EYE_DIAGRAME_OPTIONS_ENUM.GET_RESULT then
        ret = get_result(req.DataType)
    end

    return ret
end

local function is_macro_and_lane_valid(macro_id, lane_id)
    if macro_id < 0 or macro_id > 6 then
        return false
    end
    -- 当macro_id为5/6时，lan_id的范围在[0, 1]
    if macro_id == 5 or macro_id == 6 then
        return lane_id >= 0 and lane_id <= 1
    end

    --当macro_id为[0, 4]时，lan_id只能为0
    return lane_id == 0
end

local function get_four_eye_diagram(req, ctx)
    local ret = msg.GetPCIeEyeDiagramRsp.new(comp_code.Success, 0, 0, 0, 0, 0)
    local serdes_drv = drv.new()
    local args = drv.SERDES_EYE_ARGS_S.new()
    args.chip_id = req.ChipId
    args.macro_id = req.MacroId
    args.start_sds_id = req.LaneId
    args.scan_mode = req.ScanMode == 0xff and args.scan_mode or req.ScanMode
    args.pre_tap = req.PreTap == 0xff and args.pre_tap or req.PreTap
    args.pattern_type = req.PatternType == 0xff and args.pattern_type or req.PatternType
    args.eye_type = req.EyeType == 0xff and args.eye_type or req.EyeType
    args.path_type = req.PathType == 0xff and args.path_type or req.PathType
    args.eye_win = req.Win == 0xffffffff and args.eye_win or req.Win
    args.eye_thr = req.BerThr == 0x7fffffff and args.eye_thr or req.BerThr
    args.eye_avg_num = 1
    local ok, result = pcall(serdes_drv.get_four_eye_diagram, serdes_drv, args)
    if not ok then
        log:error('get four eye diagram failed, err = %s', result)
        ret.CompletionCode = comp_code.DataNotAvailable
        return ret
    end
    ret.ManufactureId = req.ManufactureId
    ret.Bottom = result.bottom
    ret.Top = result.top
    ret.Left = result.left
    ret.Right = result.right
    return ret
end

-- 预留全眼图的ipmi接口，目前暂无需求实现
local function get_full_eye_diagram(req, ctx)
    log:error('TestMode[%s] is not supported', req.TestMode)
    return msg.GetPCIeEyeDiagramRsp.new(comp_code.InvalidFieldRequest, 0, 0, 0, 0, 0)
end

local TEST_MODE_OPTIONS<const> = {
    [0x00] = get_four_eye_diagram,
    [0x01] = get_full_eye_diagram
}

local function check_params(req)
    if req.ChipId ~= 0 then
        log:error("ChipId[%s] is invalid", req.ChipId)
        return false
    end
    if not is_macro_and_lane_valid(req.MacroId, req.LaneId) then
        log:error("MacroId[%s], LaneId[%s] is invalid", req.MacroId, req.LaneId)
        return false
    end

    if not TEST_MODE_OPTIONS[req.TestMode] then
        log:error('TestMode[%s] is invalid', req.TestMode)
        return false
    end

    if not SCAN_MODE_OPTIONS[req.ScanMode] then
        log:error('ScanMode[%s] is invalid', req.ScanMode)
        return false
    end

    if not PRE_TAP_OPTIONS[req.PreTap] then
        log:error('PreTap[%s] is invalid', req.PreTap)
        return false
    end

    if not PATTERN_TYPE_OPTIONS[req.PatternType] then
        log:error('PatternType[%s] is invalid', req.PatternType)
        return false
    end

    if not EYE_TYPE_OPTIONS[req.EyeType] then
        log:error('EyeType[%s] is invalid', req.EyeType)
        return false
    end

    if not PATH_TYPE_OPTIONS[req.PathType] then
        log:error('PathType[%s] is invalid', req.PathType)
        return false
    end

    return true
end

function eye_diagrame.get_pcie_eye_diagrame(req, ctx)
    log:debug("eye diagrame TestMode: %s, ScanMode: %s, PreTap: %s, PatternType: %s, EyeType: %s, PathType: %s",
        req.TestMode, req.ScanMode, req.PreTap, req.PatternType, req.EyeType, req.PathType)

    if not check_params(req) then
        return msg.GetPCIeEyeDiagramRsp.new(comp_code.ParmOutOfRange, 0, 0, 0, 0, 0)
    end

    return TEST_MODE_OPTIONS[req.TestMode](req, ctx)
end

return eye_diagrame