-- 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 log = require 'mc.logging'
local ctx = require 'mc.context'
local bs = require 'mc.bitstring'
local skynet = require 'skynet'
local enums = require 'macros.power_mgmt_enums'
local class = require 'mc.class'
local canbus_base = require 'device_tree.adapters.power_mgmt.protocol.canbus'
local canbus = class(canbus_base)

local E_OK = nil -- 函数执行成功返回nil
local E_FAILED = '' -- 空错误信息
local CANBUS_CAN0_CHANNEL <const>  = 0
local CANBUS_CAN1_CHANNEL <const> = 1
local CANBUS_RETRY_COUNT <const> = 3

local can_data_info = bs.new([[<<
    cnt:1,
    reserve:6,
    ms:1,
    cmd:8,
    addr:7,
    protocol:6,
    frame_type:3,
    sigid_and_error:16/big,
    data/string
>>]])

function canbus:ctor(psu_slot, protocol_name)
    if not psu_slot['PsuChip'][enums.PSU_CHIP_OBJECT.BLOCKIO] or
       not psu_slot['PsuBackupChip'][enums.PSU_CHIP_OBJECT.BLOCKIO] then
        log:error('canbus:ctor PsuChip error') --主备路都需要配置
        return
    end
    canbus.cmd.CANBUS_CMD_PSU_SET_TIME =  0x8-- psu模块设置时间
    self.canbus_chip = {
        current_channel = (self.ps_id % 2 == 0) and CANBUS_CAN1_CHANNEL or CANBUS_CAN0_CHANNEL,--奇数槽是使用can0，偶数槽使用can1
        main_channel_fail_nums = 0, --主通道访问失败次数
        can0_chip = psu_slot['PsuChip'][enums.PSU_CHIP_OBJECT.BLOCKIO], --can0设备句柄存储
        can1_chip = psu_slot['PsuBackupChip'][enums.PSU_CHIP_OBJECT.BLOCKIO], --can1设备句柄存储
    }
    self.canbus_chip.main_channel = self.canbus_chip.current_channel --初始化阶段和current_channel一致
    self.canbus_chip.psu_chip = (self.canbus_chip.main_channel == CANBUS_CAN1_CHANNEL) and
                                    self.canbus_chip.can1_chip or self.canbus_chip.can0_chip --psu_chip存储当前使用的can设备句柄
    log:notice('psid: %s main_channel %s', self.ps_id, self.canbus_chip.main_channel)
end

function canbus:canbus_ms_channel_switch(flag)
    if flag == 0 then --flag定义同 QUERY_INFO_OK
        self.canbus_chip.main_channel_fail_nums = 0 --本次轮询访问成功，失败次数清0
        return
    end
    self.canbus_chip.main_channel_fail_nums = self.canbus_chip.main_channel_fail_nums + 1
    self.utils:frequency_limit_log(enums.LOG_LEVEL.NOTICE, 60,
            'psid %s canbus_ms_channel_switch %s', self.ps_id, self.canbus_chip.main_channel_fail_nums)
    if self.canbus_chip.main_channel_fail_nums <= 12 then
        return --失败次数小于13次，不处理,TPSU10秒钟会自愈一次，BMC这里处理大于10秒就行
    end
    if self.canbus_chip.current_channel ~= self.canbus_chip.main_channel then
        return --通道已经切换到备用通道不处理
    end
    self.canbus_chip.current_channel = (self.canbus_chip.main_channel == CANBUS_CAN0_CHANNEL) and
                                                            CANBUS_CAN1_CHANNEL or CANBUS_CAN0_CHANNEL
    log:notice('psid: %s change channel to %s', self.ps_id, self.canbus_chip.current_channel)
end

function canbus:canbus_check_ms_channel(channel)
    local canbus_send_data = {
        cnt = 0,
        reserve = self.slot_addr,
        ms = 1,
        cmd = self.cmd.CMD_QUERY,
        addr = self.slot_addr,
        protocol = 0x3f,
        frame_type = 4,
        sigid_and_error = 0x5 | (0 << 12), --固件查询0x5版本号
        data = string.rep(string.char(0), 6),
    }
    for i = 1, CANBUS_RETRY_COUNT do
        -- write data
        local ok, value = pcall(function()
            if channel == CANBUS_CAN0_CHANNEL then
                return self.canbus_chip.can0_chip:WriteRead(ctx.new(), can_data_info:pack(canbus_send_data), 0)
            end
            return self.canbus_chip.can1_chip:WriteRead(ctx.new(), can_data_info:pack(canbus_send_data), 0)
        end)
        -- read data
        if ok and value then
            return E_OK
        end
        skynet.sleep(10 * i)  -- 最长600ms
    end
    return E_FAILED
end

function canbus:canbus_recover_to_main_channel()
    if self.canbus_chip.current_channel == self.canbus_chip.main_channel then
        return
    end
    if self:canbus_check_ms_channel(self.canbus_chip.main_channel) == E_OK then
        self.canbus_chip.current_channel = self.canbus_chip.main_channel
        self.canbus_chip.main_channel_fail_nums = 0
        log:notice('psid: %s recover to main channel %s', self.ps_id, self.canbus_chip.current_channel)
    end
end

function canbus:chip_write_read(canbus_send_data)
    log:info('chip_write_read start, cmd %d, sigid_and_error %d, psid: %s', canbus_send_data.cmd,
             canbus_send_data.sigid_and_error, self.ps_id)
    self.canbus_chip.psu_chip = (self.canbus_chip.current_channel == CANBUS_CAN1_CHANNEL) and
                                    self.canbus_chip.can1_chip or self.canbus_chip.can0_chip
    if self.canbus_chip.psu_chip == nil then
        return nil
    end
    for i = 1, CANBUS_RETRY_COUNT do
        -- write data
        local ok, value = pcall(function()
            return self.canbus_chip.psu_chip:WriteRead(ctx.new(), can_data_info:pack(canbus_send_data), 0)
        end)
        -- read data
        if ok and value then
            return value
        end
        log:info('chip_write_read ok %s value %s retry_count %d psid: %s', ok, value, i, self.ps_id)
        skynet.sleep(10 * i)  -- 最长600ms
    end
    local err_str = table.concat({ '[canbus]commad(0x', string.format('%02x', canbus_send_data.cmd), ') failed' })
    log:info(err_str)
    return nil
end

return canbus