-- 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: 执行I2C升级CPLD资源全检
local smc_interface = require 'unit_manager.class.logic_fw.upgrade.interface.smc_interface'
local skynet = require 'skynet'
local context = require 'mc.context'
local log = require 'mc.logging'
local vos = require 'utils.vos'
local cmn = require 'common'
local utils = require 'mc.utils'
local bs = require "mc.bitstring"
local file_sec = require 'utils.file'
local client = require 'general_hardware.client'
local valid_handle = require 'unit_manager.class.logic_fw.upgrade.valid'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local cpld_space_test = require 'cpld_space_test'
local chip_lock_singleton = require 'chip_lock'

local I2C_SET_SPI = 0x01
local I2C_UPGRADE_CMD = 0x02
local I2C_WRITE_LEN = 256
local max_write_len = 0X60000
local SKU_EF2 = 1
local NOT_FORWARD_I2C_WRITE_LEN <const> = 6
local FORWARD_I2C_WRITE_LEN = 128
local I2C_WRITE_DELAY = 4
local FORWARD_I2C_WRITE_DELAY = 2 --级联场景数据转接，20ms可以满足要求

local MANUFACTURE <const> = {
    anlu = 1,
    pango = 2
}

local I2C_UPGRADE_MODE <const> = 3

local cpld_space_test_iic = {}

local function get_applicable_component_fw(system_id, cfg, component_fw_list)
    local update_obj_list = {}
    if not cfg.uid then
        log:error('unsupport firmware: uid not exist')
        return update_obj_list
    end
    -- 比较所有组件的UId是否跟该升级包支持的UId匹配
    for _, fw in ipairs(component_fw_list) do
        if (system_id == defs.ALL_SYSTEM_ID or system_id == fw.system_id) and
            cfg:check_fw_uid_exist(fw) then
            table.insert(update_obj_list, fw)
        end
    end
    return update_obj_list
end

local function retry_chip_write(wait, retries, chip, cmd, data)
    local ok = false
    local rsp
    for _ = 1, retries do
        ok, rsp = pcall(function ()
            return chip:Write(context.new(), cmd, data)
        end)
        if ok then
            return ok, rsp
        end
        cmn.skynet.sleep(wait)
    end
    return ok, rsp
end

local function set_spi_mode(chip)
    local data = '\xf0'
    return retry_chip_write(10, 10, chip, I2C_SET_SPI, data)
end

local function set_by_pass(chip)
    local data = '\x06\x00'
    return retry_chip_write(10, 10, chip, I2C_UPGRADE_CMD, data)
end

local function erase_sector(chip, index)
    local data = '\xD8' .. string.char(index) .. '\x00\x00\x00'
    return retry_chip_write(10, 10, chip, I2C_UPGRADE_CMD, data)
end

local function erase_flash(chip)
    -- 擦除扇区,当前6个扇区
    --厂商为安陆且sku类型为EF2时，擦除4个扇区
    local count = (max_write_len == 0x40000) and 3 or 5
    log:notice('The number of sectors to be erased is %s', count + 1)
    for i = 0, count do
        set_by_pass(chip)
        local ok, error = erase_sector(chip, i)
        cmn.skynet.sleep(400)
        if not ok then
            log:error('[CPLD] erase sector(%s) failed, error : %s', i, error)
            return false
        end
    end
    return true
end

local function reboot_cpld(chip)
    local data1 = '\x30'
    local data2 = '\x02\x06\x00'
    local ok, rsp = retry_chip_write(10, 10, chip, I2C_SET_SPI, data1)
    if not ok then
        log:error('[CPLD]reboot cpld send data(0x30) failed')
        return false, rsp
    end
    return retry_chip_write(10, 10, chip, I2C_UPGRADE_CMD, data2)
end

local function get_delay_and_write_len(position)
    if #position <= NOT_FORWARD_I2C_WRITE_LEN then
        -- 非级联场景硬盘背板最大写入量为256
        return I2C_WRITE_DELAY, I2C_WRITE_LEN
    else
        -- 级联场景需要额外header，data长度取128
        return FORWARD_I2C_WRITE_DELAY, FORWARD_I2C_WRITE_LEN
    end
end

local function upgrade_cpld_anlu(chip, upg_file_path, position)
    if not chip then
        log:error('[CPLD]i2c upgrade chip is invailed')
        return defs.RET.ERR
    end
    local delay, written_len = get_delay_and_write_len(position)
    -- 获取升级文件
    local file, error = file_sec.open_s(upg_file_path, 'rb')
    if not file then
        log:error('upg_file not exist, error:%s', error)
        return defs.RET.ERR
    end
    local data = utils.close(file, pcall(file.read, file, '*a'))
    log:notice('[CPLD]step 1:get file data successfully, file len :%s', #data)
    -- 配置SPI模式
    local ok, error = set_spi_mode(chip)
    if not ok then
        log:error('[CPLD] set SPI mode failed, error : %s', error)
        return defs.RET.ERR
    end
    log:notice('[CPLD]step 2:set SPI mode successfully')
    ok = erase_flash(chip)
    if not ok then
        return defs.RET.ERR
    end
    log:notice('[CPLD]step 3:erase sector successfully')
    local ctx = context.new()
    ctx.Timeout = 300 --plug超时时间设置为5分钟
    ok = skynet.unpack(chip:PluginRequest(ctx, 'general_hardware', 'iic_upgrade_anlu',
        skynet.packstring(upg_file_path, max_write_len, delay, written_len)))
    if not ok then
        return defs.RET.ERR
    end
    log:notice('[CPLD]step 4:flash write successfully')
    return defs.RET.OK
end

local function set_bypass(chip, index)
    -- 0为写使能，1为去使能
    local cmd = 0x51 + index
    local data = ''
    return retry_chip_write(10, 10, chip, cmd, data)
end

local function set_flash_state(chip, index)
    -- 0为休眠，1为唤醒
    local cmd = 0x70 + index
    local data = ''
    return retry_chip_write(10, 10, chip, cmd, data)
end

-- 紫光I2C文件传输数据域格式
local pango_transfer_file_data = bs.new([[<<
    offset:24/big,
    data/string
>>]])

local function read_state(chip)
    local data = '\xa3\x00\x00\x00'
    local _, rsp = pcall(function ()
        return chip:WriteRead(context.new(), data, 4)
    end)
    local wake, busy
    -- 第19位为是否wake,第21位为是否busy
    wake = string.sub(rsp, 3, 3):byte() & 16
    busy = string.sub(rsp, 3, 3):byte() & 4
    return wake, busy
end


local START_WRITE_ADRESS <const> = 0x38800 --黄金区为0~0x38799，不允许写入
local START_WRITE_CMD <const> = 0x040000 -- 活动区开始的堆页地址
local function transfer_data(chip, data, length, system_id, firmware_type, parameters)
    -- 写入区域，每次写入256字节,堆地址为[18:17],页地址为[16:8]，每个堆452个页
    local offset = START_WRITE_ADRESS
    local cmd = 0x20
    local page_index = 0
    local heap_index = 0
    local written_len = I2C_WRITE_LEN
    -- 写入区域从0x040000开始，写入891次
    local str, pack_data, write_adress, ok, err, progress, new_progress
    -- 开始升级后进度从40%开始变化
    progress = 40
    while offset < length do
        str = string.sub(data, offset + 1, offset + written_len)
        write_adress = START_WRITE_CMD + (page_index << 8) + (heap_index << 17)
        pack_data = pango_transfer_file_data:pack({
            offset = write_adress,
            data = str
        })
        ok, err = retry_chip_write(10, 10, chip, cmd, pack_data)
        if not ok then
            log:error('[CPLD]write flash(%s) failed, error: %s', offset, err)
            return false
        end
        log:notice('offect:%s, page_index:%s, heap_index:%s', offset, page_index, heap_index)
        new_progress = 40 + 55 * (offset - START_WRITE_ADRESS) // (length - START_WRITE_ADRESS)
        -- 进度每增加5%更新一次
        if new_progress > progress + 5 then
            progress = new_progress
            cmn.skynet.fork(function ()
                client:UpdateServiceUpdateServiceUpdateUpgradeStatus(
                    context.new(), system_id, firmware_type, 0, progress, 'Running', parameters)
            end)
        end
        offset = offset + written_len
        if page_index + 1 == 452 then
            page_index = 0
            heap_index = heap_index + 1
        else
            page_index = page_index + 1
        end
    end
    return true
end

local function upgrade_cpld_pango(chip, upg_file_path, system_id, firmware_type, parameters)
    if not chip then
        log:error('[CPLD]i2c upgrade chip is invailed')
        return defs.RET.ERR
    end
    -- 获取升级文件
    local file, error = file_sec.open_s(upg_file_path, 'rb')
    if not file then
        log:error('upg_file not exist, error:%s', error)
        return defs.RET.ERR
    end
    local data = utils.close(file, pcall(file.read, file, '*a'))
    local file_len = #data
    log:notice('[CPLD]step 1:get file data successfully, file len :%s', file_len)

    --写使能
    set_bypass(chip, 0)
    -- 查询状态寄存器
    local wake, busy =  read_state(chip)
    -- 唤醒寄存器
    if wake == 0 then
        log:notice('[CPLD]step 2: wake chip:%s, busy:%s', wake, busy)
        set_flash_state(chip, 1)
    end

    log:notice('[CPLD]step 3:start flash write')
    -- 判断状态开始写入
    local ok = transfer_data(chip, data, file_len, system_id, firmware_type, parameters)
    if not ok then
        log:error('[CPLD] write flash failed')
        set_flash_state(chip, 0)
        set_bypass(chip, 1)
        return defs.RET.ERR
    end
    log:notice('[CPLD]step 4:write successfully, start sleep chip')
    -- 休眠寄存器
    set_flash_state(chip, 0)
    wake, busy = read_state(chip)
    log:notice('[CPLD]sleep chip:%s, busy:%s', wake, busy)

    --写去使能
    set_bypass(chip, 1)
    return defs.RET.OK
end

local function set_smc_accessibility(logic_fw, smc_obj, accessibility, timeout)
    if not smc_obj then
        log:error('Set smc accessibility failed, smc_obj is nil')
        return
    end

    local ok, err = pcall(smc_obj.SetAccessibility, smc_obj, context.new(), accessibility, timeout)
    if ok then
        log:notice('set %s smc chip accessibility %s successfully.', logic_fw.device_name, accessibility)
    else
        log:error('set %s smc chip accessibility %s failed, error: %s.', logic_fw.device_name, accessibility, err)
    end
end

local function get_cpld_check_result(logic_fw, test_obj, chip_index, case_index)
    local ok, result
    if test_obj and test_obj.RefMode == 'GPIO' then
        ok, result = pcall(gpio.read, gpio_drv, test_obj.RefBusIndex, 0)
        if not ok then
            log:error('Read gpio failed, %s', result)
        end
    else
        local retry = 0
        ok, result = pcall(function()
            return test_obj.CpldResultRegister
        end)
        while not ok do
            skynet.sleep(300)
            ok, result = pcall(function()
                return test_obj.CpldResultRegister
            end)
            retry = retry + 1
            if retry == 3 then
                break
            end
        end
    end
    local check_result = (ok and result == 0) and true or false
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, '%s cpld%s test case%s %s.',
        logic_fw.device_name, chip_index, case_index, (check_result and 'pass' or 'fail'))
    return check_result
end

local function i2c_load_cpld_process(fw, test_obj, release_path, system_id, firmware_type, parameters, manufacture, sku_id)
    local index = test_obj.CpldIndex
    local ret = 0
    local check_flag = true
    local check_result = true
    local cpld_case_path
    for k, v in ipairs(test_obj.TestFirmwares) do
        cpld_case_path = string.format('%s%s', release_path, v)
        log:notice('[CPLD]get test file and start upgrade, %s', v)
        if manufacture == MANUFACTURE.anlu then
            --厂商为安陆且sku类型为EF2时，最大写入量限制为0x40000
            max_write_len = (sku_id == SKU_EF2) and 0x40000 or 0x60000
            ret = upgrade_cpld_anlu(fw.i2c_upgrade_chip, cpld_case_path, fw.position)
        elseif manufacture == MANUFACTURE.pango then
            max_write_len = 0x60000
            ret = upgrade_cpld_pango(fw.i2c_upgrade_chip, cpld_case_path, system_id, firmware_type, parameters)
        end
        if ret == defs.RET.OK then
            local ok, error = reboot_cpld(fw.i2c_upgrade_chip)
            if not ok then
                log:error('[CPLD]reboot cpld failed, error: %s', error)
                check_flag = false
                break
            end
            log:notice('[CPLD]reboot cpld successfully')
            skynet.sleep(test_obj.WaitTime // 10)
            check_result = get_cpld_check_result(fw, test_obj, index, k)
        else
            log:error('Write %s to flash failed.', v)
            check_flag = false
            break
        end

        if not check_result then
            check_flag = false
        end

    --所有case都测试通过才被认为检测成功
    local result = check_flag and
        cpld_space_test.STATUS.SUCCESS or cpld_space_test.STATUS.FAIL
    cpld_space_test.save_cpld_test_result(fw, index, result)
    end
end

local function recover_cpld_process(fw, test_obj, release_path, system_id, firmware_type, parameters, manufacture, sku_id)
    local ret = 0
    local recover_path = string.format('%s%s', release_path, test_obj.RefreshFirmware)
    log:notice('[CPLD]get recover file and start upgrade, %s', test_obj.RefreshFirmware)
    if manufacture == MANUFACTURE.anlu then
        --厂商为安陆且sku类型为EF2时，最大写入量限制为0x40000
        max_write_len = (sku_id == SKU_EF2) and 0x40000 or 0x60000
        ret = upgrade_cpld_anlu(fw.i2c_upgrade_chip, recover_path, fw.position)
    elseif manufacture == MANUFACTURE.pango then
        max_write_len = 0x60000
        ret = upgrade_cpld_pango(fw.i2c_upgrade_chip, recover_path, system_id, firmware_type, parameters)
    end

    if ret == defs.RET.OK then
        local ok, error = reboot_cpld(fw.i2c_upgrade_chip)
        if not ok then
            ret = defs.RET.ERR
            log:error('[CPLD]reboot cpld failed, error: %s', error)
        end
    end

    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, '%s cpld%s recover %s.',
        fw.device_name, test_obj.CpldIndex, (ret == defs.RET.OK and 'successfully' or 'failed'))
end

local function i2c_load_cpld(logic_fw, cfg, release_path, system_id, firmware_type, parameters)
    if not logic_fw.smc_chip then
        log:error('[CPLD]no smc chip, can not get upgrade info')
        return defs.RET.ERR
    end
    local smc_itf = smc_interface.new(logic_fw.smc_chip["bmc.kepler.Chip.BlockIO"])
    local ok, upgrade_info = pcall(function ()
        return smc_itf:get_cpld_upgrade_info()
    end)
    if not ok or upgrade_info ~= I2C_UPGRADE_MODE then
        log:error('[CPLD]Cpld uprade info is not i2c, upgrade_info:%s', upgrade_info)
        return defs.RET.ERR
    end
    local _, manufacture, sku_id = pcall(function ()
        return smc_itf:get_cpld_manufature()
    end)

    -- 解决老cpld版本兼容性问题，避免cpld不支持get_cpld_manufature获取sku_id能力导致升级失败
    if logic_fw.sub_component_sku and logic_fw.sub_component_sku ~= 255 then -- SubComponentSku默认值为255
        sku_id = logic_fw.sub_component_sku
    end

    -- 获取关联的RefSmcChip
    local smc_obj
    for _, v in ipairs(logic_fw.test_object) do
        if v.RefSmcChip then
            smc_obj = v.RefSmcChip
            break
        end
    end

    if smc_obj then
        -- 停止CPLD芯片关联的SMC通道访问，超时时间1800s
        set_smc_accessibility(logic_fw, smc_obj, false, 1800)
    end

    -- 遍历单个固件关联的所有testobj，启动升级
    for _, test_obj in ipairs(logic_fw.test_object) do
        i2c_load_cpld_process(logic_fw, test_obj, release_path, system_id, firmware_type, parameters, manufacture, sku_id)
    end

    -- 无论测试结果如何，均回复逻辑固件，必须等所有CPLD检测完成再进行恢复，否则会读取结果异常
    for _, test_obj in ipairs(logic_fw.test_object) do
        recover_cpld_process(logic_fw, test_obj, release_path, system_id, firmware_type, parameters, manufacture, sku_id)
    end

    if smc_obj then
        -- 停止CPLD芯片关联的SMC通道访问，超时时间1800s
        set_smc_accessibility(logic_fw, smc_obj, true, 1)
    end

    return defs.RET.OK
end

local function update_chip_lock(ctx, lock_time, logic_fw)
    return pcall(function(...)
        return chip_lock_singleton.get_instance():lock(logic_fw.update_lock_chip, ctx, lock_time)
    end)
end

local function update_chip_unlock(ctx, logic_fw)
    return pcall(function(...)
        return chip_lock_singleton.get_instance():unlock(logic_fw.update_lock_chip, ctx)
    end)
end

function cpld_space_test_iic.load_test_cpld_with_lock_iic(logic_fw, cfg, release_path, system_id, firmware_type, parameters)
    local lock_time = 600
    local ret
    if not logic_fw.test_object or #logic_fw.test_object == 0 then
        log:error('get space test object list empty, fw id = %s.', logic_fw.id)
        cpld_space_test.save_cplds_test_result(logic_fw, logic_fw.test_chip_nums, cpld_space_test.STATUS.FAIL)
        return defs.RET.OK
    end

    while lock_time > 0 do
        local ok, ret_code = update_chip_lock(context.new(), 600, logic_fw)
        if ok and ret_code == defs.RET.OK then
            ret = i2c_load_cpld(logic_fw, cfg, release_path, system_id, firmware_type, parameters)
            ok, ret_code = update_chip_unlock(context.new(), logic_fw)
            if not ok or ret_code ~= defs.RET.OK then
                log:error('unlock %s cpld chip failed, ret = %s.', logic_fw.device_name, ret_code)
            end
            return ret
        end
        lock_time = lock_time - 1
        skynet.sleep(100)
    end

    cpld_space_test.save_cplds_test_result(logic_fw, logic_fw.test_chip_nums, cpld_space_test.STATUS.FAIL)
    return defs.RET.OK
end

return cpld_space_test_iic