-- 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 NVME_MI_OPTIONS = require 'nvme.nvme_mi_protocol.nvme_mi_def'
local sf = string.format

local ABSOLUTE_ZERO_CELSIUS <const> = -273
local SET <const> = '+'
local CLEAR <const> = '-'

local nvme_log = {}

local function kelvin_to_celsius(temp)
    return temp + ABSOLUTE_ZERO_CELSIUS
end

local function write_supported_log_pages(fp, obj)
    if not next(obj) then
        return
    end
    local sup_log_sort = {}
    for k in pairs(obj) do
        table.insert(sup_log_sort, k)
    end
    table.sort(sup_log_sort, function(a, b)
        return tonumber(a) < tonumber(b)
    end)
    local lid, support
    fp:write(sf('Support Log Pages Deatils\n'))
    for _, v in ipairs(sup_log_sort) do
        lid, support = v, obj[v]
        fp:write(sf('LID 0x%x (%s), supports 0x%x\n',
            lid, NVME_MI_OPTIONS.LOG_TO_STRING[lid] or 'Unknown', support))
    end
    fp:write('\n')
end

local function write_error_log(fp, objs)
    if not next(objs) then
        return
    end
    local field
    fp:write('Error Log\n')
    fp:write('.................\n')
    for entry, obj in ipairs(objs) do
        fp:write(sf(' Entry[%s]   \n', entry - 1))
        fp:write('.................\n')
        fp:write(sf('error_count     : %s\n', obj.error_count))
        fp:write(sf('sqid            : %s\n', obj.sqid))
        fp:write(sf('cmdid           : %s\n', obj.cmdid))
        field = obj.status_field
        fp:write(sf('status_field    : %s(%s)\n',
            field >> 1, NVME_MI_OPTIONS.NVME_STATUS_TO_STRING[(field >> 1) & 0x7ff]) or 'Unknown')
        fp:write(sf('phase_tag       : %s\n', field & 0x1))
        fp:write(sf('parm_err_loc    : %s\n', obj.parm_error_location))
        fp:write(sf('lba             : %s\n', obj.lba))
        fp:write(sf('vs              : %s\n', obj.vsia))
        fp:write(sf('cs              : %s\n', obj.csi))
        fp:write(sf('trtype_spec_info: %s\n', obj.trtype_spec_info))
        fp:write('.................\n')
    end
    fp:write('\n')
end

local function sensor_print(fp, temp, idx)
    if temp == 0 then
        return
    end
    fp:write(sf('Temperature Sensor %s                    : %s C (%s Kelvin)\n',
        idx, kelvin_to_celsius(temp), temp))
end

local function write_smart_log(fp, obj)
    if not next(obj) then
        return
    end
    fp:write('Smart Log\n')
    fp:write(sf('critical_warning                        : %s\n', obj.critical_warning))
    fp:write(sf('       Available Spare[0]               : %s\n', obj.critical_warning & 0x01))
    fp:write(sf('       Temp. Threshold[1]               : %s\n', (obj.critical_warning & 0x02) >> 1))
    fp:write(sf('       NVM subsystem Reliability[2]     : %s\n', (obj.critical_warning & 0x04) >> 2))
    fp:write(sf('       Read-only[3]                     : %s\n', (obj.critical_warning & 0x08) >> 3))
    fp:write(sf('       Volatile mem. backup failed[4]   : %s\n', (obj.critical_warning & 0x10) >> 4))
    fp:write(sf('       Persistent Mem. RO[5]            : %s\n', (obj.critical_warning & 0x20) >> 5))
    fp:write(sf('temperature                             : %s C (%s Kelvin)\n',
        kelvin_to_celsius(obj.temperature), obj.temperature))
    fp:write(sf('available_spare                         : %s%s\n', obj.avail_spare, '%'))
    fp:write(sf('available_spare_threshold               : %s%s\n', obj.spare_thresh, '%'))
    fp:write(sf('percentage_used                         : %s%s\n', obj.percent_used, '%'))
    fp:write(sf('endurance group critical warning summary: %s\n', obj.endu_grp_crit_warn_sumry))
    fp:write(sf('data_units_read                         : %s\n', obj.data_units_read))
    fp:write(sf('data_units_written                      : %s\n', obj.data_units_written))
    fp:write(sf('host_read_commands                      : %s\n', obj.host_reads))
    fp:write(sf('host_write_commands                     : %s\n', obj.host_writes))
    fp:write(sf('controller_busy_time                    : %s\n', obj.ctrl_busy_time))
    fp:write(sf('power_cycles                            : %s\n', obj.power_cycles))
    fp:write(sf('power_on_hours                          : %s\n', obj.power_on_hours))
    fp:write(sf('unsafe_shutdowns                        : %s\n', obj.unsafe_shutdowns))
    fp:write(sf('media_errors                            : %s\n', obj.media_errors))
    fp:write(sf('num_err_log_entries                     : %s\n', obj.num_err_log_entries))
    fp:write(sf('Warning Temperature Time                : %s\n', obj.warning_temp_time))
    fp:write(sf('Critical Composite Temperature Time     : %s\n', obj.critical_comp_time))
    sensor_print(fp, obj.temp_sensor1, 1)
    sensor_print(fp, obj.temp_sensor2, 2)
    sensor_print(fp, obj.temp_sensor3, 3)
    sensor_print(fp, obj.temp_sensor4, 4)
    sensor_print(fp, obj.temp_sensor5, 5)
    sensor_print(fp, obj.temp_sensor6, 6)
    sensor_print(fp, obj.temp_sensor7, 7)
    sensor_print(fp, obj.temp_sensor8, 8)
    fp:write(sf('Thermal Management T1 Trans Count       : %s\n', obj.thm_temp1_trans_count))
    fp:write(sf('Thermal Management T2 Trans Count       : %s\n', obj.thm_temp2_trans_count))
    fp:write(sf('Thermal Management T1 Total Time        : %s\n', obj.thm_temp1_total_time))
    fp:write(sf('Thermal Management T2 Total Time        : %s\n', obj.thm_temp2_total_time))
    fp:write('\n')
end

local function print_frs(fp, val, idx)
    if not val then
        return
    end
    fp:write(sf('frs%s : %s\n', idx, val))
end

local function write_fw_log(fp, obj)
    if not next(obj) then
        return
    end
    fp:write('Firmware Log\n')
    fp:write(sf('afi  : 0x%x\n', obj.afi))
    print_frs(fp, obj.frs1, 1)
    print_frs(fp, obj.frs2, 2)
    print_frs(fp, obj.frs3, 3)
    print_frs(fp, obj.frs4, 4)
    print_frs(fp, obj.frs5, 5)
    print_frs(fp, obj.frs6, 6)
    print_frs(fp, obj.frs7, 7)
    fp:write('\n')
end

local function write_feature_identifiers(fp, obj)
    if not next(obj) then
        return
    end
    local fid, fsp, support
    local fid_sort = {}
    for k in pairs(obj) do
        table.insert(fid_sort, k)
    end
    table.sort(fid_sort, function(a, b)
        return tonumber(a) < tonumber(b)
    end)
    fp:write('FID Supports Effects Log\n')
    fp:write('Admin Command Set\n')
    for _, v in ipairs(fid_sort) do
        fid, support = v - 1, obj[v]
        fp:write(sf('FID %02x -> Support Effects Log: %08x', fid, support))
        fp:write(sf('  FSUPP+'))
        fp:write(sf('  UDCC%s', support & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.UDCC ~= 0 and SET or CLEAR))
        fp:write(sf('  NCC%s', support & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.NCC ~= 0 and SET or CLEAR))
        fp:write(sf('  NIC%s', support & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.NIC ~= 0 and SET or CLEAR))
        fp:write(sf('  CCC%s', support & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.CCC ~= 0 and SET or CLEAR))
        fp:write(sf('  USS%s', support & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.UUID_SEL_SUP ~= 0 and SET or CLEAR))
        fsp = (support >> NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.SCOPE_SHIFT) &
            NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.SCOPE_MASK
        fp:write(sf('  NAMESPACE SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.NS_SCOPE ~= 0 and SET or CLEAR))
        fp:write(sf('  CONTROLLER SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.CTRL_SCOPE ~= 0 and SET or CLEAR))
        fp:write(sf('  NVM SET SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.NVM_SET_SCOPE ~= 0 and SET or CLEAR))
        fp:write(sf('  ENDURANCE GROUP SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.ENDGRP_SCOPE ~= 0 and SET or CLEAR))
        fp:write(sf('  DOMAIN SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.DOMAIN_SCOPE ~= 0 and SET or CLEAR))
        fp:write(sf('  NVM Subsystem SCOPE%s',
            fsp & NVME_MI_OPTIONS.FID_SUPPORTED_EFFECTS.NSS_SCOPE ~= 0 and SET or CLEAR))
        fp:write('\n')
    end
    fp:write('\n')
end

local function write_ctrl_health(fp, obj)
    if not next(obj) then
        return
    end
    fp:write(sf('Response Entries : %s\n', obj.entries))
    for _, v in ipairs(obj.info) do
        fp:write('----------------------------\n')
        fp:write(sf('Controller Identifier            : %s\n', v.ctlid))
        fp:write(sf('Controller Status                : 0x%04x\n', v.csts))
        fp:write(sf('Composite Temperature            : %s Kelvins\n', v.ctemp))
        fp:write(sf('Percentage Used                  : %s%s\n', v.pdlu, '%'))
        fp:write(sf('Available Spare                  : %s\n', v.spare))
        fp:write(sf('Critical Warning                 : %s\n', v.cwarn))
        fp:write(sf('Controller Health Status Changed : %s\n', v.chsc))
        fp:write('----------------------------\n')
    end
    fp:write('\n')
end

local function write_mctp_trans_unit_size(fp, obj)
    if not next(obj) then
        return
    end
    fp:write(sf('MCTP Transmission Unit Size: %s\n', obj.size))
    fp:write('\n')
end

local function write_nvm_subsystem_info(fp, obj)
    if not next(obj) then
        return
    end
    fp:write(sf('Number of Ports                   : %s\n', obj.nump))
    fp:write(sf('NVMe-MI Major Version Number      : %s\n', obj.mjr))
    fp:write(sf('NVMe-MI Minor Version Number      : %s\n', obj.mnr))
    fp:write(sf('NVMe-MI NVM Subsystem Capabilities: %s\n', obj.subsyscap))
    fp:write('\n')
end
local function write_subsys_health_status(fp, obj)
    if not next(obj) then
        return
    end
    fp:write(sf('NVM Subsystem Status       : 0x%02x\n', obj.nss))
    fp:write(sf('SMART Warnings             : 0x%02x\n', obj.sw))
    fp:write(sf('Composite Temperature      : %s degrees Celsius\n', obj.ctemp))
    fp:write(sf('Percentage Drive Life Used : %s%s\n', obj.pdlu, '%'))
    fp:write(sf('Composite Controller Status: 0x%04x\n', obj.ccs))
    fp:write('\n')
end

function nvme_log.dump_log(self, fp_w)
    write_supported_log_pages(fp_w, self.supported_log_pages)
    write_error_log(fp_w, self.error_log)
    write_smart_log(fp_w, self.smart_log)
    write_fw_log(fp_w, self.fw_log)
    write_feature_identifiers(fp_w, self.feature_identifiers)
    write_ctrl_health(fp_w, self.controller_health_status)
    write_mctp_trans_unit_size(fp_w, self.mctp_trans_unit_size)
    write_nvm_subsystem_info(fp_w, self.nvm_subsystem_info)
    write_subsys_health_status(fp_w, self.subsys_health_status)
end

function nvme_log.dump_smart_log(self, fp_w)
    write_smart_log(fp_w, self.smart_log)
end

return nvme_log
