-- 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 NVME_MI_OPTIONS = require 'nvme.nvme_mi_protocol.nvme_mi_def'
local nvme_utils = require 'nvme.utils'
local skynet = require 'skynet'

local nvme_mi_command = {}

function nvme_mi_command.pkg_ctrl_health_stat_dword(data)
    local dword_info = {
        sctlid = 0,
        maxrent = 0,
        incf = 0,
        incpf = 0,
        incvf = 0,
        report_all = 0,
        csts = 0,
        ctemp = 0,
        pdlu = 0,
        spare = 0,
        cwartn = 0,
        ccf = 0
    }
    for k, v in pairs(data) do
        if dword_info[k] then
            dword_info[k] = v
        end
    end
    local dword0 = dword_info.sctlid | (dword_info.maxrent << 16) | (dword_info.incf << 24) |
        (dword_info.incpf << 25) | (dword_info.incvf << 26) | (dword_info.report_all << 31)

    local dword1 = dword_info.csts | (dword_info.ctemp << 1) | (dword_info.pdlu << 2) |
        (dword_info.spare << 3) | (dword_info.cwartn << 4) | (dword_info.ccf << 31)
    return {dword0 = dword0, dword1 = dword1}
end

function nvme_mi_command.pkg_config_get_dword(data)
    local dword_info = {
        port_id = 0,
        config_id = 0
    }
    for k, v in pairs(data) do
        if dword_info[k] then
            dword_info[k] = v
        end
    end
    local dword0 = dword_info.port_id << 24 | dword_info.config_id
    return {dword0 = dword0, dword1 = 0}
end

local function update_controller_health_status(obj)
    local controller_health_status
    obj.queue(function ()
        pcall(function ()
            controller_health_status = obj.nvme_mi_obj:ControllerHealthStatus():value()
        end)
    end)
    if not controller_health_status or not next(controller_health_status) then
        log:error('get Disk%s controller_health_status failed', obj.nvme.Slot)
        return
    end

    log:info('get Disk%s controller_health_status successfully', obj.nvme.Slot)
    obj.controller_health_status = controller_health_status
end

local function update_mctp_trans_unit_size(obj)
    local mctp_trans_unit_size
    obj.queue(function ()
        pcall(function ()
            mctp_trans_unit_size = obj.nvme_mi_obj:MctpTransSize():value()
        end)
    end)
    if not mctp_trans_unit_size or not next(mctp_trans_unit_size) then
        log:error('get Disk%s mctp_trans_unit_size failed', obj.nvme.Slot)
        return
    end

    log:info('get Disk%s mctp_trans_unit_size successfully', obj.nvme.Slot)
    obj.mctp_trans_unit_size = mctp_trans_unit_size
end

local function update_nvm_subsystem_info(obj)
    local nvm_subsystem_info
    obj.queue(function ()
        pcall(function ()
            nvm_subsystem_info = obj.nvme_mi_obj:SubsystemInfo():value()
        end)
    end)
    if not nvm_subsystem_info or not next(nvm_subsystem_info) then
        log:error('get Disk%s nvm_subsystem_info failed', obj.nvme.Slot)
        return
    end

    log:info('get Disk%s nvm_subsystem_info successfully', obj.nvme.Slot)
    obj.nvm_subsystem_info = nvm_subsystem_info
end

local function update_subsys_health_status(obj)
    local subsys_health_status
    obj.queue(function ()
        pcall(function ()
            subsys_health_status = obj.nvme_mi_obj:SubsystemHealthStatus():value()
        end)
    end)
    if not subsys_health_status or not next(subsys_health_status) then
        log:error('get Disk%s subsys_health_status failed', obj.nvme.Slot)
        return
    end

    log:info('get Disk%s nvm_subsystem_info successfully', obj.nvme.Slot)
    obj.subsys_health_status = subsys_health_status
end

function nvme_mi_command.update_nvme_mi_command_info(obj)
    update_controller_health_status(obj)
    update_mctp_trans_unit_size(obj)
    update_nvm_subsystem_info(obj)
    update_subsys_health_status(obj)
end

local function package_nvmemi_data(data)
    if #data[4] == 0 then
        return NVME_MI_OPTIONS.nvmemi_command_request:pack({
            opcode = data[1],
            rsvd1 = 0,
            rsvd2 = 0,
            dword0 = data[2],
            dword1 = data[3]
        })
    end
    local request_data = nvme_utils.little_endian_str(data[4])
    return NVME_MI_OPTIONS.nvmemi_command_request_extend:pack({
        opcode = data[1],
        rsvd1 = 0,
        rsvd2 = 0,
        dword0 = data[2],
        dword1 = data[3],
        data = request_data
    })
end

function nvme_mi_command.send_raw_nvme_mi_command(obj, data)
    local req_data = package_nvmemi_data(data)
    local ret
    obj.nvme_mi_mctp_obj.queue(function ()
        pcall(function ()
            ret = obj.nvme_mi_mctp_obj.nvme_mi_obj:RawRequest({
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = req_data
            }):value()
        end)
    end)
    if not ret then
        log:error('Disk%s send_raw_nvme_mi_command failed', obj.Slot)
        return
    end
    local rsp = NVME_MI_OPTIONS.nvmemi_command_response:unpack(ret, true)
    local rsp_data = {}
    if rsp.data then
        rsp_data = nvme_utils.little_endian_table(rsp.data)
    end
    return rsp.status, rsp.nvmersq, rsp_data
end

function nvme_mi_command.get_controller_list(obj)
    local try_count = 0
    local MAX_COUNT<const> = 3
    local ctrl_id
    while try_count < MAX_COUNT do
        obj.queue(function ()
            pcall(function ()
                ctrl_id = obj.nvme_mi_obj:ReadCtrlList():value()
            end)
        end)
        if ctrl_id then
            log:notice('get Disk%s controller id:%s successfully', obj.nvme.Slot, ctrl_id)
            obj.ctrl_id = ctrl_id
            return
        end
        try_count = try_count + 1
        skynet.sleep(100)
    end
    log:error('get Disk%s controller id failed', obj.nvme.Slot)
end

return nvme_mi_command