-- 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 skynet = require 'skynet'
local smc_interface = require 'unit_manager.class.logic_fw.upgrade.interface.smc_interface'
local cmn = require 'common'
local log = require 'mc.logging'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local vos = require 'utils.vos'
local utils = require 'mc.utils'
local file_sec = require 'utils.file'
local context = require 'mc.context'
local bs = require "mc.bitstring"
local client = require 'general_hardware.client'
local valid_handle = require 'unit_manager.class.logic_fw.upgrade.valid'

local I2C_SET_SPI = 0x01
local I2C_UPGRADE_CMD = 0x02
local I2C_WRITE_LEN = 128
local max_write_len = 0X60000
local SKU_EF2 = 1

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

local I2C_UPGRADE_MODE <const> = 3

local i2c_process = {}

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个扇区
    local count = 5
    --厂商为安陆且sku类型为EF2时，擦除4个扇区
    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 upgrade_cpld_anlu(chip, upg_file_path)
    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'))
    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')
    ok = skynet.unpack(chip:PluginRequest(context.new(), 'general_hardware', 'iic_upgrade_anlu',
        skynet.packstring(upg_file_path, max_write_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 ok, 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)
    --写去使能
    set_bypass(chip, 1)
    return defs.RET.OK
end

local function i2c_load_cpld(fw, release_path, system_id, firmware_type, parameters)
    if not 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(fw.smc_chip)
    local ok, upgrade_info, manufacture, sku_id
    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
    ok, manufacture, sku_id = pcall(function ()
        return smc_itf:get_cpld_manufature()
    end)
    -- 启动升级
    log:notice('[CPLD]get cpld obj and start upagrade, cpld%02x.bin', manufacture)
    local upgrade_path = string.format('%scpld%02x.bin', release_path, manufacture)
    if manufacture == MANUFACTURE.anlu then
        --厂商为安陆且sku类型为EF2时，最大写入量限制为0x40000
        max_write_len = (sku_id == SKU_EF2) and 0x40000 or 0x60000
        return upgrade_cpld_anlu(fw.i2c_upgrade_chip, upgrade_path)
    elseif manufacture == MANUFACTURE.pango then
        max_write_len = 0x60000
        return upgrade_cpld_pango(fw.i2c_upgrade_chip, upgrade_path, system_id, firmware_type, parameters)
    end
    return defs.RET.ERR
end

local function check_and_upgrade(fw, release_path, system_id, firmware_type, parameters)
    local lock_time_out = 600
    local ok, ret_code
    while lock_time_out > 0 do
        ok, ret_code = fw:i2c_update_chip_lock(require 'mc.context'.new(), 600)
        if ok and ret_code == 0 then
            local upgrade_ret = i2c_load_cpld(fw, release_path, system_id, firmware_type, parameters)
            -- 硬件总线解锁
            ok, ret_code = fw:i2c_update_chip_unlock(require 'mc.context'.new())
            if not ok or ret_code ~= 0 then
                log:error("[iic cpld upgrade] set lock status to 0 failed, ret code:%s", ret_code)
            end
            return upgrade_ret
        end
        log:debug("[iic cpld upgrade] get chip lock failed")
        lock_time_out = lock_time_out - 1
        skynet.sleep(100)
    end

    log:error("[iic cpld upgrade] upgrade failed because can not get chip lock")
    return defs.RET.ERR
end

function i2c_process:iic_upgrade_cpld(db, fw_list, cfg_list, file_path, system_id, firmware_type, parameters)
    if not fw_list or not cfg_list then
        log:error('firmware or cfg not exist')
        return defs.RET.ERR, false
    end
    if (not file_path) or (not vos.get_file_accessible(file_path)) then
        log:error('error file_path')
        return defs.RET.ERR, false
    end
    -- 输入路径/dev/shm/upgrade/Firmware
    local s, e = string.find(file_path, '/.+/')
    local release_path = string.sub(file_path, s, e) -- 提取路径/dev/shm/upgrade/
    local fw_name = string.sub(file_path, e + 1, string.len(file_path)) -- 提取文件名Firmware
    local one_fw_ret = defs.RET.OK
    local final_ret = defs.RET.OK
    local is_need_valid = false
    for _, cfg in ipairs(cfg_list) do
        -- 找到与文件名匹配cfg的节点
        if cfg.name ~= fw_name then
            goto continue
        end
        -- 通过Uid找到适用的组件对象
        local component_fw_list = get_applicable_component_fw(system_id, cfg, fw_list)
        if #component_fw_list == 0 then
            log:error('no match component: firmware uid = %s', cfg.uid)
            return defs.RET.ERR, is_need_valid
        end
        -- uid适用情况下，解压在与Firmware同级文件夹
        utils.secure_tar_unzip(
            file_path,
            release_path,
            0x6400000, -- 最大解压限制100M
            1024
        )
        log:notice('[cpld]get cpld packages successful')
        for _, fw in ipairs(component_fw_list) do
            one_fw_ret = check_and_upgrade(fw, release_path, system_id, firmware_type, parameters)
            -- 设置生效标志
            if one_fw_ret == defs.RET.OK then
                log:running(log.RLOG_INFO, 'Upgrade %s Firmware successfully', fw.name)
                valid_handle:set_validating_flag(db, fw.system_id, 1)
                is_need_valid = true
            end
            if one_fw_ret == defs.RET.ERR then
                log:running(log.RLOG_ERROR, 'Upgrade %s Firmware failed', fw.name)
                final_ret = one_fw_ret
            end
        end
        -- 目前框架一次只发一个Firmware，使用完就可以退出了
        break
        ::continue::
    end
    return final_ret, is_need_valid
end

return i2c_process