-- 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: 执行网卡固件升级

local skynet = require 'skynet'
local log = require 'mc.logging'
local vos = require 'utils.vos'
local utils = require 'mc.utils'
local upgrade_defs = require 'upgrade.upgrade_defs'
local utils_core = require 'utils.core'
local client = require 'client'
local parser_cfg = require 'upgrade.parser_cfg'
local _, file_sec = pcall(require, 'utils.file')
local _, mtd_api = pcall(require, 'mtd.drv')
local _, spi_flash = pcall(require, 'libmgmt_protocol.bios.infrastructure.spi_flash')
local c_network_adapter_firmware = require 'upgrade.network_adapter_firmware_object'

local upgrade_flash = {}

-- 获取升级列表
local function get_upgrade_list(cfg)
    if not cfg then
        return nil
    end

    local res_list = {}
    c_network_adapter_firmware.collection:fold(function(acc, obj, key)
        if obj.ComponentID == cfg.component_id and obj.ComponentIDEx == cfg.component_idex then
            log:notice("check network adapter firmware id(%s) id_ex(%s) exist success",
                obj.ComponentID, obj.ComponentIDEx)
            table.insert(res_list, obj)
        end
    end, {})

    log:notice("get upgrade list success, cnt = %s", #res_list)
    return res_list
end

function upgrade_flash.set_network_adapter_firmware_route(upgrade_obj, route_value)
    if not upgrade_obj then
        error('upgrade_obj is nil')
    end
    local cnt = 0
    while cnt < upgrade_defs.SET_ROUTE_RETRY do
        log:notice('set network adapter firmware route to %s cnt %s times', route_value, cnt)
        local ok = pcall(function()
            upgrade_obj.Route = route_value
        end)
        if ok then
            log:notice('set network adapter firmware route to %s successfully', route_value)
            return
        end
        cnt = cnt + 1
        skynet.sleep(100)
    end
    error(string.format('set network adapter firmware route failed %s times', cnt))
end

local function install_spi_driver(upgrade_obj)
    if vos.get_file_accessible(upgrade_defs.DEVNAME_MTD) then -- 首先检查当前是否存在已加载的设备（避免bios加载时误升级）
        error('[spi]device ' .. upgrade_defs.DEVNAME_MTD .. 'already exists, spi driver can not be installed again')
    end
    upgrade_flash.set_network_adapter_firmware_route(upgrade_obj, upgrade_obj.FirmwareRoute) -- 设置网卡芯片通道路由
    local mhz = string.format('g_clk_mhz=%s', upgrade_defs.PERIOD3_MHZ)
    spi_flash.insmod_driver(upgrade_defs.SPIKODRV, mhz, upgrade_defs.DEVNAME_MTD, client, client.PProxyDriverInsmod) --加载spi驱动
    skynet.sleep(100)
    spi_flash.check_device_ready(upgrade_defs.DEVNAME_MTD) -- 检查驱动加载情况
end

local function write_file_to_flash(upgrade_obj, file_path, device_fd)
    local erase_length = upgrade_obj.FlashSizeKiB
    local write_length = utils_core.stat(file_path).st_size
    if write_length > erase_length then
        error(string.format('[Network Adapter Upgrade] illegal write_length:%s, larger than erase_length:%s', write_length, erase_length))
    end
    local file_fd, err = file_sec.open_s(file_path, 'r')
    if not file_fd then
        error(string.format('[Network Adapter Upgrade]open nic upgrade package failed: %s', err))
    end
    local ok
    ok, err = pcall(function()
        log:notice('[Network Adapter Upgrade] start erasing and writing flash')
        mtd_api.erase(device_fd, upgrade_defs.FLASH_START_ADDRESS, erase_length)
        file_fd:seek('set', upgrade_defs.FLASH_START_ADDRESS)
        local data = file_fd:read(write_length)
        mtd_api.write(device_fd, upgrade_defs.FLASH_START_ADDRESS, data, write_length)
        log:notice('[Network Adapter Upgrade] writing finished and start to read')
        if data ~= mtd_api.read(device_fd, upgrade_defs.FLASH_START_ADDRESS, write_length) then -- 升级后回读校验
            error('[Network Adapter Upgrade] data read from flash differ from write')
        end
    end)
    utils.safe_close_file(file_fd, function()
        log:notice('[Network Adapter Upgrade] safe close file finished')
    end)
    if not ok then
        error(string.format('[Network Adapter Upgrade] erase and write flash failed, failed info:%s', err))
    end
end

local function close_remove_driver_and_reset_route(device_fd, upgrade_obj)
    if device_fd then
        mtd_api.close(device_fd)
    end
    spi_flash.rmmod_driver(upgrade_defs.SPIKODRV, upgrade_defs.DEVNAME_MTD, client, client.PProxyDriverRmmod)
    upgrade_flash.set_network_adapter_firmware_route(upgrade_obj, upgrade_obj.DefaultRoute)
end

function upgrade_flash.network_adapter_upgrade_flash(self, file_path)
    -- 解析出升级目录和文件名
    local release_path, firmware_name = parser_cfg.extract_upgrade_file(file_path)

    -- 解析出文件路径对应的cfg配置文件，并获取升级列表
    self.upgrade_list = get_upgrade_list(self.cfgs[firmware_name])
    if not self.upgrade_list or not next(self.upgrade_list) then
        log:error('[Network Adapter Upgrade] No fw need to upgrade during process stage')
        return upgrade_defs.RET.ERR
    end

    -- 解压固件，最大解压限制100M
    utils.secure_tar_unzip(file_path, release_path, 0x6400000, 1024)

    local dst_path = string.format('%s%s', release_path, 'nic.bin')
    for _, upgrade_obj in ipairs(self.upgrade_list) do
        local ok = pcall(function()
            install_spi_driver(upgrade_obj)
        end)
        if not ok then
            log:error('[Network Adapter Upgrade] install spi driver failed')
            pcall(function()
                close_remove_driver_and_reset_route(nil, upgrade_obj) -- 失败后卸载
            end)
            return upgrade_defs.RET.ERR
        end

        local device_fd = mtd_api.open(upgrade_defs.DEVNAME_MTD, upgrade_defs.O_SYNC_RDWR)
        local write_ok = pcall(function()
            write_file_to_flash(upgrade_obj, dst_path, device_fd) -- 将nic.bin写入flash
        end)

        local close_ok = pcall(function()
            close_remove_driver_and_reset_route(device_fd, upgrade_obj) -- 升级后,无论成功、失败都需卸载设备及驱动
        end)

        if not write_ok or not close_ok then
            log:error('[Network Adapter Upgrade]write flash or close failed')
            log:running(log.RLOG_ERROR, 'Upgrade Nic Firmware failed by route: %s', upgrade_obj.FirmwareRoute)
            return upgrade_defs.RET.ERR
        end
        log:running(log.RLOG_INFO, 'Upgrade Nic Firmware successfully by route: %s', upgrade_obj.FirmwareRoute)
    end

    return upgrade_defs.RET.OK
end

return upgrade_flash
