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

local NVME_NSID_ALL <const> = 0xffffffff
local NVME_NO_LOG_LSP <const> = 0
local BUFFER_SIZE_4 <const> = 4
local BUFFER_SIZE_16 <const> = 16
local BUFFER_SIZE_64 <const> = 64
local BUFFER_SIZE_512 <const> = 0x200
local BUFFER_SIZE_1024  <const> = 0x400
local BUFFER_SIZE_4096  <const> = 0x1000

local smart_log_para = {
    lpo = 0,
    nsid = NVME_NSID_ALL,
    uuid_ix = 0,
    csi = 0,
    data_len = BUFFER_SIZE_512,
    lsi = 0,
    ot = false,
    rae = false,
    numd = (BUFFER_SIZE_512 >> 2) - 1,
    numdu = ((BUFFER_SIZE_512 >> 2) - 1) >> 16,
    numdl = ((BUFFER_SIZE_512 >> 2) - 1) & 0xffff
}

local fid_para = {
    lpo = 0,
    uuid_ix = 0,
    csi = 0,
    nsid = 0,
    data_len = BUFFER_SIZE_1024,
    lsi = 0,
    ot = false,
    rae = false,
    numd = (BUFFER_SIZE_1024 >> 2) - 1,
    numdu = ((BUFFER_SIZE_1024 >> 2) - 1) >> 16,
    numdl = ((BUFFER_SIZE_1024 >> 2) - 1) & 0xffff
}

local fw_log_para = {
    lpo = 0,
    uuid_ix = 0,
    csi = 0,
    nsid = NVME_NSID_ALL,
    data_len = BUFFER_SIZE_512,
    lsi = 0,
    ot = false,
    rae = true,
    numd = (BUFFER_SIZE_512 >> 2) - 1,
    numdu = ((BUFFER_SIZE_512 >> 2) - 1) >> 16,
    numdl = ((BUFFER_SIZE_512 >> 2) - 1) & 0xffff
}

local supported_log_pages_para = {
    lpo = 0,
    uuid_ix = 0,
    csi = 0,
    nsid = NVME_NSID_ALL,
    data_len = BUFFER_SIZE_1024,
    lsi = 0,
    ot = false,
    rae = false,
    numd = (BUFFER_SIZE_1024 >> 2) - 1,
    numdu = ((BUFFER_SIZE_1024 >> 2) - 1) >> 16,
    numdl = ((BUFFER_SIZE_1024 >> 2) - 1) & 0xffff
}

local error_log_pages_para = {
    lpo = 0,
    uuid_ix = 0,
    csi = 0,
    nsid = NVME_NSID_ALL,
    data_len = BUFFER_SIZE_4096,
    lsi = 0,
    ot = false,
    rae = false,
    numd = (BUFFER_SIZE_4096 >> 2) - 1,
    numdu = ((BUFFER_SIZE_4096 >> 2) - 1) >> 16,
    numdl = ((BUFFER_SIZE_4096 >> 2) - 1) & 0xffff
}

local nvme_mi = {
    protocol_dependencies = { nvme_mi_standard = { endpoint = nil } },
    properties = {
        ReadCtrlList = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.nvmemi_command_request:pack({
                    opcode = NVME_MI_OPTIONS.MI_COMMAND_OPCODE.READ_MI_DATA_STRUCTRUE,
                    rsvd1 = 0,
                    rsvd2 = 0,
                    dword0 = NVME_MI_OPTIONS.DATA_STRUCTURE_TYPE.CONTROLLER_LIST << 24,
                    dword1 = 0
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.controller_list_format:unpack(data, true)
                return rsp.ctrl_id
            end
        },
        GetLogPage = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0
            },
            response = function(data)
                return nvme_utils.parse_rsp_status(data)
            end
        },
        Identify = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0
            },
            response = function(data)
                return nvme_utils.parse_rsp_status(data)
            end
        },
        SmartLog = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = smart_log_para.nsid,
                    data_len = smart_log_para.data_len,
                    data_offset = 0,
                    cdw10 = NVME_MI_OPTIONS.LOG_PAGE_IDENTIFIERS.SMART_INFORMATION |
                        (smart_log_para.numdl << 16) | (smart_log_para.rae and 1 << 15 or 0) | NVME_NO_LOG_LSP << 8,
                    cdw11 = smart_log_para.numdu | (smart_log_para.lsi << 16),
                    cdw12 = smart_log_para.lpo & 0xffffffff,
                    cdw13 = smart_log_para.lpo >> 32,
                    cdw14 = smart_log_para.uuid_ix | (smart_log_para.ot and 1 << 23 or 0) | smart_log_para.csi << 24
                })
            },
            response = function(data)
                return data
            end
        },
        FeatureIdentifiers = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = fid_para.nsid,
                    data_len = fid_para.data_len,
                    data_offset = 0,
                    cdw10 = NVME_MI_OPTIONS.LOG_PAGE_IDENTIFIERS.FEATURE_IDENTIFIERS_SUPPORTED_EFFECTS |
                        (fid_para.numdl << 16) | (fid_para.rae and 1 << 15 or 0) |
                        NVME_NO_LOG_LSP << 8,
                    cdw11 = fid_para.numdu | (fid_para.lsi << 16),
                    cdw12 = fid_para.lpo & 0xffffffff,
                    cdw13 = fid_para.lpo >> 32,
                    cdw14 = fid_para.uuid_ix | (fid_para.ot and 1 << 23 or 0) |
                        fid_para.csi << 24
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.log_response:unpack(data, true)
                data = rsp.data
                local i = 1
                local len = #data
                local tab = {}
                local fid, info
                while len and len ~= 0 and i <= (len // BUFFER_SIZE_4) do
                    rsp = NVME_MI_OPTIONS.buffur_size_4_response:unpack(data, true)
                    fid, info = rsp.data, rsp.info
                    if fid & 1 == 1 then
                        tab[i] = fid
                    end
                    data = info
                    i = i + 1
                end
                return tab
            end
        },
        FwLog = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = fw_log_para.nsid,
                    data_len = fw_log_para.data_len,
                    data_offset = 0,
                    cdw10 = NVME_MI_OPTIONS.LOG_PAGE_IDENTIFIERS.FIRMWARE_SLOT_INFORMATION |
                        (fw_log_para.numdl << 16) | (fw_log_para.rae and 1 << 15 or 0) | NVME_NO_LOG_LSP << 8,
                    cdw11 = fw_log_para.numdu | (fw_log_para.lsi << 16),
                    cdw12 = fw_log_para.lpo & 0xffffffff,
                    cdw13 = fw_log_para.lpo >> 32,
                    cdw14 = fw_log_para.uuid_ix | (fw_log_para.ot and 1 << 23 or 0) | fw_log_para.csi << 24
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.fw_log_response:unpack(data, true)
                return {
                    afi = rsp.afi,
                    frs1 = tonumber(rsp.frs1),
                    frs2 = tonumber(rsp.frs2),
                    frs3 = tonumber(rsp.frs3),
                    frs4 = tonumber(rsp.frs4),
                    frs5 = tonumber(rsp.frs5),
                    frs6 = tonumber(rsp.frs6),
                    frs7 = tonumber(rsp.frs7)
                }
            end
        },
        SupportedLogPages = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = supported_log_pages_para.nsid,
                    data_len = supported_log_pages_para.data_len,
                    data_offset = 0,
                    cdw10 = NVME_MI_OPTIONS.LOG_PAGE_IDENTIFIERS.SUPPORTED_LOG_PAGES |
                        (supported_log_pages_para.numdl << 16) |
                        (supported_log_pages_para.rae and 1 << 15 or 0) | NVME_NO_LOG_LSP << 8,
                    cdw11 = supported_log_pages_para.numdu | (supported_log_pages_para.lsi << 16),
                    cdw12 = supported_log_pages_para.lpo & 0xffffffff,
                    cdw13 = supported_log_pages_para.lpo >> 32,
                    cdw14 = supported_log_pages_para.uuid_ix | (supported_log_pages_para.ot and 1 << 23 or 0) |
                        supported_log_pages_para.csi << 24
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.log_response:unpack(data, true)
                data = rsp.data
                local i = 0
                local len = #data
                local tab = {}
                local sup_log, info
                while len and len ~= 0 and i < (len // BUFFER_SIZE_4) do
                    rsp = NVME_MI_OPTIONS.buffur_size_4_response:unpack(data, true)
                    sup_log, info = rsp.data, rsp.info
                    if sup_log & 1 == 1 then
                        tab[i] = sup_log
                    end
                    data = info
                    i = i + 1
                end
                return tab
            end
        },
        ErrorInformation = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_GET_LOG_PAGE,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = error_log_pages_para.nsid,
                    data_len = error_log_pages_para.data_len,
                    data_offset = 0,
                    cdw10 = NVME_MI_OPTIONS.LOG_PAGE_IDENTIFIERS.ERROR_INFORMATION |
                        (error_log_pages_para.numdl << 16) |
                        (error_log_pages_para.rae and 1 << 15 or 0) | NVME_NO_LOG_LSP << 8,
                    cdw11 = error_log_pages_para.numdu | (error_log_pages_para.lsi << 16),
                    cdw12 = error_log_pages_para.lpo & 0xffffffff,
                    cdw13 = error_log_pages_para.lpo >> 32,
                    cdw14 = error_log_pages_para.uuid_ix | (error_log_pages_para.ot and 1 << 23 or 0) |
                        error_log_pages_para.csi << 24
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.log_response:unpack(data, true)
                data = rsp.data
                local i = 1
                local len = #data
                local tab = {}
                while len and len ~= 0 and i <= (len // BUFFER_SIZE_64) do
                    rsp = NVME_MI_OPTIONS.error_log_response:unpack(data, true)
                    tab[i] = {
                        error_count = rsp.error_count,
                        sqid = rsp.sqid,
                        cmdid = rsp.cmdid,
                        status_field = rsp.status_field,
                        parm_error_location = rsp.parm_error_location,
                        lba = rsp.lba,
                        nsid = rsp.nsid,
                        vsia = rsp.vsia,
                        trtype = rsp.trtype,
                        csi = rsp.csi,
                        trtype_spec_info = rsp.trtype_spec_info
                    }
                    data = rsp.info
                    i = i + 1
                end
                return tab
            end
        },
        ControllerHealthStatus = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.nvmemi_command_request:pack({
                    opcode = NVME_MI_OPTIONS.MI_COMMAND_OPCODE.CONTROLLER_HEALTH_STATUS_POLL,
                    rsvd1 = 0,
                    rsvd2 = 0,
                    dword0 = nvme_mi_command.pkg_ctrl_health_stat_dword({incpf = 1, report_all = 1}).dword0,
                    dword1 = nvme_mi_command.pkg_ctrl_health_stat_dword({incpf = 1, report_all = 1}).dword1
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.mi_ctrl_health_status_rsp:unpack(data, true)
                data = rsp.data
                local i = 1
                local len = #data
                local tab = {entries = rsp.entries, info = {}}
                while rsp.entries ~= 0 and i <= (len // BUFFER_SIZE_16) do
                    rsp = NVME_MI_OPTIONS.mi_ctrl_health_status_data:unpack(data, true)
                    tab.info[i] = {
                        ctlid = rsp.ctlid,
                        csts = rsp.csts,
                        ctemp = rsp.ctemp,
                        pdlu = rsp.pdlu,
                        spare = rsp.spare,
                        cwarn = rsp.cwarn,
                        chsc = rsp.chsc
                    }
                    data = rsp.info
                    i = i + 1
                end
                return tab
            end
        },
        SubsystemHealthStatus = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.nvmemi_command_request:pack({
                    opcode = NVME_MI_OPTIONS.MI_COMMAND_OPCODE.NVM_SUBSYSTEM_HEALTH_STATUS_POLL,
                    rsvd1 = 0,
                    rsvd2 = 0,
                    dword0 = 0,
                    dword1 = 0 << 31
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.subsystem_health_status:unpack(data, true)
                return {
                    nss = rsp.nss,
                    sw = rsp.sw,
                    ctemp = rsp.ctemp,
                    pdlu = rsp.pdlu,
                    ccs = rsp.ccs
                }
            end
        },
        SubsystemInfo = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.nvmemi_command_request:pack({
                    opcode = NVME_MI_OPTIONS.MI_COMMAND_OPCODE.READ_MI_DATA_STRUCTRUE,
                    rsvd1 = 0,
                    rsvd2 = 0,
                    dword0 = NVME_MI_OPTIONS.DATA_STRUCTURE_TYPE.NVM_SUBSYSTEM_INFORMATION << 24,
                    dword1 = 0
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.subsystem_info:unpack(data, true)
                return {
                    nump = rsp.nump,
                    mjr = rsp.mjr,
                    mnr = rsp.mnr,
                    subsyscap = rsp.subsyscap
                }
            end
        },
        MctpTransSize = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_MI_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.nvmemi_command_request:pack({
                    opcode = NVME_MI_OPTIONS.MI_COMMAND_OPCODE.CONFIGURATION_GET,
                    rsvd1 = 0,
                    rsvd2 = 0,
                    dword0 = nvme_mi_command.pkg_config_get_dword({
                        config_id = NVME_MI_OPTIONS.NVME_MI_CONFIG_IDENT.MCTP_TRANSMISSION_UNIT_SIZE
                    }).dword0,
                    dword1 = nvme_mi_command.pkg_config_get_dword({
                        config_id = NVME_MI_OPTIONS.NVME_MI_CONFIG_IDENT.MCTP_TRANSMISSION_UNIT_SIZE
                    }).dword1
                })
            },
            response = function(data)
                data = nvme_utils.parse_rsp_status(data)
                if not data then
                    return
                end
                local rsp = NVME_MI_OPTIONS.unit_size_rsp:unpack(data, true)
                return {size = rsp.size}
            end
        },
        IdentifyNS = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {
                message_type = NVME_MI_OPTIONS.NVME_MI_MESSAGE_TYPE.NVME_ADMIN_COMMAND,
                command_slot_identifier = 0,
                data = NVME_MI_OPTIONS.log_request:pack({
                    opcode = NVME_MI_OPTIONS.NVME_ADMIN_COMMANDS_OPCODE.NVME_ADMIN_IDENTIFY,
                    flags = 0x3,
                    ctrl_id = 1,
                    nsid = NVME_NSID_ALL,
                    data_len = BUFFER_SIZE_4096,
                    data_offset = 0,
                    cdw10 = 0,
                    cdw11 = 0,
                    cdw12 = 0,
                    cdw13 = 0,
                    cdw14 = 0
                })
            },
            response = function(data)
                return data
            end
        },
        RawRequest = {
            protocol = 'nvme_mi_standard',
            action = 'on_demand',
            request = {},
            response = function(data)
                return data
            end
        }
    }
}

return {
    mctp = function(endpoint)
        local obj = utils.table_copy(nvme_mi)
        obj.protocol_dependencies.nvme_mi_standard.endpoint = endpoint
        return obj
    end
}
