-- 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: DDR测试
local ipmi_set_cmd = require 'manufacture.ipmi.cmds.SetDdrTestConfiguration'
local ipmi_get_cmd = require 'manufacture.ipmi.cmds.GetDdrTestResult'
local mc_ipmi = require 'ipmi'
local client = require 'manufacture.client'
local skynet = require 'skynet'
local context = require 'mc.context'
local log = require 'mc.logging'
local ddrc_ok, ddrc = pcall(require, 'libsoc_adapter.ddrc')

local cc = mc_ipmi.types.Cc
local busy_flag = false

local ipmi_handler = {}

local env_set_tbl = {
    [1] = {
        prop_name = 'RefreshCycleTimeForAllBanks',
        offset = 1,
        size = 4,
        default = 0xffffffff,
        min = 0,
        max = 0xffffffff
    },
    [2] = {
        prop_name = 'RefreshCycleTimeForPerBank',
        offset = 5,
        size = 4,
        default = 0xffffffff,
        min = 0,
        max = 0xffffffff
    },
    [3] = {
        prop_name = 'RowPrechargeTimeForAllBanks',
        offset = 9,
        size = 4,
        default = 0xffffffff,
        min = 0,
        max = 0xffffffff
    },
    [4] = {
        prop_name = 'RowPrechargeTimeForPerBank',
        offset = 13,
        size = 4,
        default = 0xffffffff,
        min = 0,
        max = 0xffffffff
    },
    [5] = {
        prop_name = 'ExmbistBitmap',
        offset = 17,
        size = 4,
        default = 0,
        min = 0,
        max = 0xffffffff
    },
    [6] = {
        prop_name = 'MarginSwitch',
        offset = 21,
        size = 1,
        default = 0,
        min = 0,
        max = 1
    },
    [7] = {
        prop_name = 'EyeDiagramSwitch',
        offset = 22,
        size = 1,
        default = 0,
        min = 0,
        max = 1
    },
    [8] = {
        prop_name = 'EyeDiagramDirection',
        offset = 23,
        size = 1,
        default = 0,
        min = 0,
        max = 4
    },
    [9] = {
        prop_name = 'EyeDiagramPattern',
        offset = 24,
        size = 1,
        default = 0,
        min = 0,
        max = 6
    }
}

local function get_ddr_object()
    if not ipmi_handler.ddr_obj then
        client:ForeachDDRObjects(
            function(o)
                ipmi_handler.ddr_obj = o
            end
        )
    end
    return ipmi_handler.ddr_obj
end

local function retry_set_parameters(param)
    if busy_flag then
        log:notice('retry set parameters is busy')
        return
    end
    busy_flag = true
    local ok, ddr_obj
    local cnt = 0
    while cnt < 10 do
        ok, ddr_obj = pcall(function ()
            return get_ddr_object()
        end)
        if ok and ddr_obj then
            break
        end
        log:error('%s cnt get ddr obj failed', cnt)
        skynet.sleep(1000)
        cnt = cnt + 1
    end
    if not ddr_obj then
        busy_flag = false
        return false
    end
    cnt = 0
    local ctx = context.get_context_or_default()
    while cnt < 10 do
        ok = ddr_obj.pcall:SetTestParameters(ctx, param)
        if ok then
            busy_flag = false
            return true
        end
        log:error('%s cnt set parameters failed', cnt)
        skynet.sleep(1000)
        cnt = cnt + 1
    end
    busy_flag = false
    return false
end

function ipmi_handler.restore_default_env()
    log:notice('restore default env start')
    if not ddrc_ok then
        log:error('ddrc_drv is not ok')
        return
    end
    skynet.fork(function ()
        local param = {}
        for _, v in ipairs(env_set_tbl) do
            param[tostring(v.prop_name)] = tonumber(v.default)
        end
        if retry_set_parameters(param) then
            log:notice('restore default env successful')
        else
            log:error('restore default env failed')
        end
    end)
end

local function get_prop_value(length, v, tail)
    local val, str_val
    if length < v.size then
        return -1
    end
    str_val = string.sub(tail, v.offset, v.offset + v.size - 1)
    if #str_val ~= v.size then
        return -1
    end
    val = string.unpack('I' .. v.size, str_val) or v.default
    return length - v.size, v.prop_name, val
end

function ipmi_handler.set_test_configuration(req, ctx)
    if not ddrc_ok then
        log:error('ddrc_drv is not ok')
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration failed")
        return ipmi_set_cmd.SetDdrTestConfigurationRsp.new(cc.CommandDisabled, req.ManufactureId, 1)
    end
    local length = req.Length
    local prop, val
    local param = {}
    if length ~= #req.Tail then
        log:error('command length %s incorrect, tail length: %s', length, #req.Tail)
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration failed")
        return ipmi_set_cmd.SetDdrTestConfigurationRsp.new(cc.ReqDataLenInvalid, req.ManufactureId, 1)
    end
    for _, v in ipairs(env_set_tbl) do
        if length == 0 then
            break
        end
        length, prop, val = get_prop_value(length, v, req.Tail)
        if length < 0 then
            log:error('tail length %s is not intact', #req.Tail)
            mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration failed")
            return ipmi_set_cmd.SetDdrTestConfigurationRsp.new(cc.ReqDataTruncated, req.ManufactureId, 1)
        end
        if val < v.min or val > v.max then
            log:error('prop %s value %s not in range (%s, %s)', v.prop_name, val, v.min, v.max)
            mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration failed")
            return ipmi_set_cmd.SetDdrTestConfigurationRsp.new(cc.ParmOutOfRange, req.ManufactureId, 1)
        end
        param[tostring(prop)] = tonumber(val)
    end
    retry_set_parameters(param)
    mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration successfully")
    return ipmi_set_cmd.SetDdrTestConfigurationRsp.new(cc.Success, req.ManufactureId, 0)
end

function ipmi_handler.get_test_result(req, ctx)
    if not ddrc_ok then
        log:error('ddrc_drv is not ok')
        mc_ipmi.ipmi_operation_log(ctx, 'manufacture', "Set DDR test configuration failed")
        return ipmi_get_cmd.GetDdrTestResultRsp.new(cc.CommandDisabled, req.ManufactureId, 0, 0)
    end
    local ddrc_drv = ddrc.new()
    local ok, exmbist_result = pcall(ddrc_drv.get_exmbist_status, ddrc_drv)
    if ok and exmbist_result then
        ddrc_drv:close()
        return ipmi_get_cmd.GetDdrTestResultRsp.new(cc.Success, req.ManufactureId, 4, exmbist_result)
    end
    ddrc_drv:close()
    return ipmi_get_cmd.GetDdrTestResultRsp.new(cc.DataNotAvailable, req.ManufactureId, 0, 0)
end

return ipmi_handler
