-- Copyright (c) 2025 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 context = require 'mc.context'
local crc32 = require 'mc.crc32'
local _, skynet = pcall(require, 'skynet')
local npu_enums = require 'unit_manager.class.unit.acu.npu_enums'
-- 一个告警转换成字符串12个字节，需要限制最大个数
local I3C_MAX_ERROR_CODE <const> = 70

local i3c = {}

-- 通过i3c写命令+数据，返回完成码
function i3c.chip_blkwrite(chip, cmd, data)
    data = string.unpack('L', data)
    local buf = string.pack('I1I3I4I4', 0X20, 4, cmd, data)
    local crc = crc32(buf, 0, true)
    data = string.pack('I4I4', data, crc)
    if not chip then
        log:error('[i3c] chip blkwrite: unknown chip')
        return
    end
    local ok, res = pcall(function ()
        chip:PluginRequest(context.get_context_or_default(), 'compute',
            'plugins_mailbox_write', skynet.packstring(cmd, data))
    end)
    if not ok then
        error(string.format('[i3c] write data failed, error message: %s', res))
    end
end

-- 通过i3c发送读命令+数据长度，返回数据
function i3c.chip_blkread(chip, cmd, len)
    if not chip then
        log:error('[i3c] chip blkread: unknown chip')
        return nil
    end
    local ok, data = pcall(function ()
        return skynet.unpack(chip:PluginRequest(context.get_context_or_default(), 'compute',
            'plugins_mailbox_read', skynet.packstring(cmd, len)))
    end)
    if not ok then
        error(string.format('[i3c]read data failed, error message: %s', data))
    end
    return data
end

-- 对i3c命令字做二值一致防抖
function i3c.read_debounce(chip, cmd, len, times)
    local ok, rsp1, rsp2

    for i = 1, times do
        ok, rsp1 = pcall(function ()
            return chip:Read(context.get_context_or_default(), cmd, len)
        end)
        skynet.sleep(1)
        if not ok or not rsp1 then
            goto next
        end

        ok, rsp2 = pcall(function ()
            return chip:Read(context.get_context_or_default(), cmd, len)
        end)
        skynet.sleep(1)
        if ok and rsp1 == rsp2 then
            break
        end
        ok = false
        ::next::
    end
    if not ok then
        log:debug('rsp1 %s is not equal to rsp2 %s', rsp1, rsp2)
        return nil
    end

    return rsp1
end

local function get_cmd(burcmd, offset)
    -- burcmd 0x01, 地址offset 0x0f1900, cmd: 0x0100190f
    local cmd = string.pack('I4', (offset << 8) | burcmd)
    return string.unpack('>I4', cmd)
end

-- 通过i3c获取模组告警状态
function i3c.get_npu_board_health(chip)
    local burcmd = 1
    local health_info = {}
    local cmd = get_cmd(burcmd, npu_enums.I3C_HEALTH_START_OFFSET)
    local res = i3c.read_debounce(chip, cmd, 5, 3)
    if not res then
        log:debug('get npu board health failed')
        return nil
    end
    health_info.health = string.unpack('I2', string.sub(res, 2, 3))
    local count = string.unpack('I2', string.sub(res, 4, 5))
    health_info.count = count > I3C_MAX_ERROR_CODE and I3C_MAX_ERROR_CODE or count
    return health_info
end

local function update_error_code_table(error_code_table, data)
    if not error_code_table or not data then
        log:debug('Parameter is invalid')
    end
    local value = 0
    local index = 1
    while index < #data do
        value = string.unpack('I4', string.sub(data, index, index + 3))
        table.insert(error_code_table, string.format('0x%02X', value))
        index = index + 4
    end
end

-- 通过i3c获取模组告警码
function i3c.get_npuboard_error_code(chip, health_info)
    local error_code_len = health_info.count * 4
    local read_len = 0
    local burcmd = 0
    local start_offset = npu_enums.I3C_ERROR_CODE_START_OFFSET
    local offset = 0
    local error_code_table = {}
    local res
    while error_code_len ~= 0 do
        read_len = error_code_len < 16 and error_code_len or 16
        burcmd = read_len // 4
        -- 获取opcode
        offset = get_cmd(burcmd, start_offset)
        res = i3c.read_debounce(chip, offset, read_len + 1, 3)
        if not res then
            log:error('get npu board error code failed')
            return nil
        end
        -- i3c返回的第一个字节是idel 0xa3
        update_error_code_table(error_code_table, string.sub(res, 2, #res))
        error_code_len = error_code_len - read_len
        start_offset = start_offset + read_len
    end
    return table.concat(error_code_table, ', ')
end

return i3c
