-- 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 skynet = require 'skynet'
local context = require 'mc.context'
local class_mgnt = require "mc.class_mgnt"
local c_object = require 'mc.orm.object'
local c_tasks = require 'mc.orm.tasks'
local log = require 'mc.logging'
local c_object_manage = require 'mc.orm.object_manage'
local utils = require 'mc.utils'
local signal = require 'mc.signal'
local utils_core = require 'utils.core'
local vos = require 'utils.vos'
local custom_messages = require 'messages.custom'
local npu_imu_cmd = require 'npu.hdk_cmd'
local fructl = require 'infrastructure.fructl'
local client = require 'network_adapter.client'
local c_vlan = require 'device.class.vlan'
local c_optical_module = require 'device.class.optical_module'
local log_netcard_info = require 'device.class.log_netcard_info'
local common_def = require 'common.common_def'
local common_func = require 'common.common_func'
local device_comm_fun = require 'device.class.nic_mgmt.comm_fun'
local device_comm_defs = require 'device.class.nic_mgmt.comm_defs'

local str_hash = utils_core.str_hash
local BUSINESSPORT_CARD_TYPE_VIR<const> = 6
local BUSINESSPORT_CARD_TYPE_TEAM<const> = 7
local BUSINESSPORT_CARD_TYPE_BRIDGE<const> = 8
local MAC_ADDR_DEFAULT<const> = '00:00:00:00:00:00'
local NCSI_FAILED_THRESHOLD = 3 -- NCSI连续失败阈值，达到后启动SMBUS备选
local INVALID_DATA_STRING<const> = 'N/A'
local PORT_LINK_UP<const> = '%s NPU%s port link up\n'
local PORT_LINK_DOWN<const> = '%s NPU%s port link down\n'
local PORT_LINK_UP_NOTICE<const> = '%s NPU%s port link up(previous link info may be lost)\n'
local PORT_LINK_DOWN_NOTICE<const> = '%s NPU%s port link down(previous link info may be lost)\n'
local NPU_PORT_TIMESTAMP_MAX_NUM<const> = 10
local NPU_PORT_PER_HOUR_PER_MODULE_LOG_NUM<const> = 60 -- 每小时每个模块日志限幅60条
local LOADED_NIC_OS<const> = 1
local LANE_PER_MACRO<const> = 4
local RX_POWER_LOWER_THRETHOLD<const> = 0.01
local LINK_DOWN_ALARM_TIME<const> = 600000
local LINK_STATUS_CHECK_TIME<const> = 720000

local c_network_port = c_object('NetworkPort')

local function is_support_ub(model)
    if model == 'NPU_UB' or model == 'NPU_UBoE' then
        return true
    else
        return false
    end
end

-- 根据网卡初始化网口的类型
local function init_port_type(network_adapter, network_port)
    if network_adapter.Type ~= common_def.network_adapter_type.OCP then
        network_port.Type = network_adapter.Type
        return
    end
    -- 如果是OCP卡需区分OCP1和OCP2
    if network_adapter.SlotNumber == 1 then
        network_port.Type = common_def.network_port_type.OCP1
    else
        network_port.Type = common_def.network_port_type.OCP2
    end
end

-- 框架收到自发现分发的对象后，在回调 on_add_object 函数前会装载持久化数据回对象，但是
-- 网口的主键依赖其所属的网卡对象，必须在这里填充好 NetworkPort 对象的主键值，确保框架从
-- 持久化能找回正确的持久化数据
function c_network_port.before_add_object(object)
    local parent = object:get_parent()
    -- 网口必须依赖其父对象网卡上树
    while not parent or parent.NodeId == '' do
        skynet.sleep(100)
        parent = object:get_parent()
    end
    init_port_type(parent, object)
    object.NetworkAdapterId = parent.NodeId
    object.NodeId =  parent.NodeId .. (object.PortID + 1)
    return false
end

local lldp_receive_mds_name_map<const> = {
    ChassisId = 'chassis_id',
    ChassisIdSubtype = 'chassis_id_subtype',
    LLDPReceivePortId = 'port_id',
    PortIdSubtype = 'port_id_subtype',
    SystemName = 'system_name',
    ManagementVlanId = 'vlan_id'
}

local default_lldp_receive_data<const> = {
    ChassisId = '',
    ChassisIdSubtype = '',
    LLDPReceivePortId = '',
    PortIdSubtype = '',
    SystemName = '',
    ManagementVlanId = 0
}

function c_network_port:restore_port_prop_without_bma()
    self:restore_speed_mbps_after_bma_stop()
    self:restore_auto_speed_negotiation_after_bma_stop()
    self:restore_full_duplex_after_bma_stop()
    self:restore_bdf_after_bma_stop()
    self:restore_firmware_version_after_bma_stop()
end

function c_network_port:restore_speed_mbps_after_bma_stop()
    if self.t_prop_without_bma.SpeedMbps then
        self.SpeedMbps = self.t_prop_without_bma.SpeedMbps
    else
        self.SpeedMbps = 4294967295
    end
end

function c_network_port:restore_auto_speed_negotiation_after_bma_stop()
    if self.t_prop_without_bma.AutoSpeedNegotiation then
        self.AutoSpeedNegotiation = self.t_prop_without_bma.AutoSpeedNegotiation
    else
        self.AutoSpeedNegotiation = 255
    end
end

function c_network_port:restore_full_duplex_after_bma_stop()
    if self.t_prop_without_bma.FullDuplex then
        self.FullDuplex = self.t_prop_without_bma.FullDuplex
    else
        self.FullDuplex = 255
    end
end

function c_network_port:restore_bdf_after_bma_stop()
    if self.t_prop_without_bma.BDF then
        self.BDF = self.t_prop_without_bma.BDF
    else
        self.BDF = ''
    end
end

function c_network_port:restore_firmware_version_after_bma_stop()
    if self.t_prop_without_bma.FirmwareVersion then
        self.FirmwareVersion = self.t_prop_without_bma.FirmwareVersion
    else
        self.FirmwareVersion = ''
    end
end

-- 从bma获取的连接状态为NoLink、LinkUp、LinkDown，其他途径获取的状态为Connected、Disconnected
local linkstatus_from_bma<const> = {
    "LinkUp",
    "NoLink",
    "LinkDown"
}

function c_network_port:is_linkstatus_from_bma(link_status)
    local ok, ret = pcall(function()
        return utils.array_contains(linkstatus_from_bma, link_status)
    end)
    if not ok then
        log:error('check linkstatus from bma failed,err = %s', ret)
        return false
    end
    return ret
end

function c_network_port:set_oem_smbus_config_obj(oem_smbus_config_obj)
    self.oem_smbus_config_obj = oem_smbus_config_obj
end

function c_network_port:set_ncsi_config_obj(ncsi_config_obj)
    self.ncsi_config_obj = ncsi_config_obj
end

function c_network_port:set_smbus_schedulers(smbus_schedulers)
    self.smbus_schedulers = smbus_schedulers
    -- 同时设置给光模块
    local op = self:get_optical_module()
    if op then
        op.smbus_schedulers = smbus_schedulers
    end
end

function c_network_port:get_port_id()
    return self.PortID
end

function c_network_port:set_properties(properties, value)
    if self[properties] then
        self[properties] = value
    end
end

-- hi182x网卡走smbus获取光模块温度调用此函数
function c_network_port:set_op_temp_hi182x(temp)
    local op = self:get_optical_module()
    if op then
        op:set_temp_hi182x(temp)
    end
end

function c_network_port:set_op_temp(temp)
    local op = self:get_optical_module()
    if op then
        op:set_temp(temp)
    end
end

function c_network_port:set_op_presence(presence)
    local op = self:get_optical_module()
    if op then
        op:set_presence(presence)
    end
end

local function update_network_port_lldp_receive(port, data)
    for k, v in pairs(lldp_receive_mds_name_map) do
        port[k] = data[v] or default_lldp_receive_data[k]
    end
end

function c_network_port:update_lldp_receive(lldp_rsp)
    -- 先清除clear task，根据获取的新的ttl创建新的clear task
    if self.lldp_clear_task then
        self.lldp_clear_task:cancel()
        self.lldp_clear_task = false
    end

    -- 如果收到ttl为0的数据，则代表数据不再有效，重置资源树属性到默认值
    if lldp_rsp.parsed_data.ttl == 0 then
        update_network_port_lldp_receive(self, {})
        return
    end
    update_network_port_lldp_receive(self, lldp_rsp.parsed_data)

    -- 根据ttl创建延时任务，到ttl秒后重置资源树属性到默认值
    self.lldp_clear_task = self:timeout_ms(lldp_rsp.parsed_data.ttl * 1000, function()
        update_network_port_lldp_receive(self, {})
    end)
end

function c_network_port:set_lldp_enable(lldp_flag)
    self.LLDPEnabled = lldp_flag
end

function c_network_port:set_full_duplex(duplex_mode)
    self.FullDuplex = duplex_mode == nil and 255 or (duplex_mode and 1 or 0)
    self.t_prop_without_bma.FullDuplex = self.FullDuplex
end

function c_network_port:set_auto_speednegotiation(negotiation_flag)
    self.AutoSpeedNegotiation = negotiation_flag == nil and 255 or (negotiation_flag and 1 or 0)
    self.t_prop_without_bma.AutoSpeedNegotiation = self.AutoSpeedNegotiation
end

function c_network_port:set_speed(speed)
    self.SpeedMbps = speed
    self.t_prop_without_bma.SpeedMbps = speed
end

function c_network_port:set_medium_type(medium_type)
    self.MediumType = medium_type
end

function c_network_port:set_firmware_version(firmware_version)
    self.FirmwareVersion = firmware_version
    self.t_prop_without_bma.FirmwareVersion = firmware_version
end

function c_network_port:get_firmware_version()
    return self.FirmwareVersion
end

function c_network_port:set_permanent_mac_addr(addr)
    self.PermanentMACAddress = addr
end

function c_network_port:set_mac_addr(addr)
    self.MACAddress = addr
end

local link_status_numeric_table = {
    ['NoLink'] = 0,
    ['LinkDown'] = 0,
    ['LinkUp'] = 1,
    ['Disconnected'] = 0,
    ['Connected'] = 1,
    ['N/A'] = 255
}

function c_network_port:set_link_status_numeric(link_status)
    -- 加pcall以适配部分nic卡和其他网卡关于LinkStatusNumeric属性的sr配置差异
    pcall(function ()
        if not link_status or not link_status_numeric_table[link_status] then
            return
        end
        
        local link_status_numeric = link_status_numeric_table[link_status]
        if self.LinkStatusNumeric == link_status_numeric then
            return
        end
        self.LinkStatusNumeric = link_status_numeric
        log:notice('%s link_status_numeric change to %s', self.NodeId, link_status_numeric)
    end)
end

function c_network_port:set_link_status(status)
    if not self:is_linkstatus_from_bma(self.LinkStatus) then
        self.LinkStatus = status
    end
    self.t_prop_without_bma.LinkStatus = status -- 用于重置bma时恢复为其他途径获取的状态
end

function c_network_port:send_cmd(command_code, bin_data)
    if next(self.ncsi_config_obj) == nil then
        log:error('mctp not start')
        return
    end
    local resp_data = self.ncsi_config_obj:Test({
        packet_type = command_code,
        data = bin_data,
        channel_id = self:get_port_id(),
        package_id = self.package_id
    }):value()
    return resp_data
end

function c_network_port:update_ncsi_port_stats()
    local s = self.ncsi_config_obj:PortMetrics({channel_id = self:get_port_id(), package_id = self.package_id})
    local u64_fields = {
        RXPauseXOFFFrames = true,
        RXPauseXONFrames = true,
        TXPauseXOFFFrames = true,
        TXPauseXONFrames = true,
        RXFalseCarrierErrors = true
    }
    self:connect_signal(s.on_data_change, function(data)
        for name in pairs(data) do
            self[name] = u64_fields[name] and data[name] or tostring(data[name])
        end
    end)
    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update ncsi port stats start', self.NodeId)
end

function c_network_port:update_ncsi_link_status()
    local ncsi_failed_count = 0

    local s = self.ncsi_config_obj:LinkStatus({channel_id = self:get_port_id(), package_id = self.package_id})
    s.on_data_change:on(function(data)
        if not self.get_ncsi_link_status_succeess then
            log:notice('update ncsi link status to %s, card:%s, portid:%s',
                data.LinkStatus, self.NetworkAdapterId, self.PortID)
            self.get_ncsi_link_status_succeess = true
        end
        self:set_link_status(data.LinkStatus)
        self:set_link_status_numeric(data.LinkStatus)
        self.SpeedMbps = data.SpeedMbps
        self.t_prop_without_bma.SpeedMbps = data.SpeedMbps
        self.AutoSpeedNegotiation = data.AutoSpeedNegotiation
        self.t_prop_without_bma.AutoSpeedNegotiation = data.AutoSpeedNegotiation
        self.FullDuplex = data.FullDuplex
        self.t_prop_without_bma.FullDuplex = data.FullDuplex
        self.ncsi_get_linkstatus_retry_count = 0
        ncsi_failed_count = 0 -- 成功时重置计数

        -- NCSI恢复正常，停止备选SMBUS任务
        if self.smbus_schedulers.link_status_task and not self.smbus_schedulers.link_status_task.is_paused then
            self.smbus_schedulers.link_status_task:pause()
            self.smbus_schedulers.link_status_task.data = nil
            log:notice('%s LinkStatus: NCSI recovered, stopped SMBUS fallback', self.NodeId)
        end
    end)
    -- 消息发不通的时候，更新网口状态。
    s.on_error:on(function()
        -- NCSI失败达到阈值时，启动SMBUS备选
        ncsi_failed_count = ncsi_failed_count + 1
        if ncsi_failed_count >= NCSI_FAILED_THRESHOLD and self.smbus_schedulers.link_status_task and
            self.smbus_schedulers.link_status_task.is_paused then
            self.smbus_schedulers.link_status_task:resume()
            log:notice('%s LinkStatus: SMBUS fallback activated successfully', self.NodeId)
        end

        self.ncsi_get_linkstatus_retry_count = self.ncsi_get_linkstatus_retry_count + 1
        if self.ncsi_get_linkstatus_retry_count > 2 then
            if self.get_ncsi_link_status_succeess then
                log:notice('get ncsi link status failed, set link_status to N/A, card:%s, port_id: %s',
                    self.NetworkAdapterId, self.PortID)
                self.get_ncsi_link_status_succeess = false
            end
            self:set_link_status(INVALID_DATA_STRING)
            self:set_link_status_numeric(INVALID_DATA_STRING)
            self.SpeedMbps = 0
            self.t_prop_without_bma.SpeedMbps = 0
            self.AutoSpeedNegotiation = 255
            self.t_prop_without_bma.AutoSpeedNegotiation = 255
            self.FullDuplex = 255
            self.t_prop_without_bma.FullDuplex = 255
            self.ncsi_get_linkstatus_retry_count = 0
        end
    end)

    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update ncsi link status start', self.NodeId)
end

function c_network_port:initialize_ncsi_channel()
    local ret = self.ncsi_config_obj:InitializeNCSIChannel({
        channel_id = self:get_port_id(),
        package_id = self.package_id
    }):value()
    if not ret then
        log:error('unable to initialize NCSI channel, channel_id: %d', self:get_port_id())
    end
end

function c_network_port:update_BDF_by_ncsi()
    if not self.ncsi_config_obj.BDF then
        return
    end
    if self.bdf_task_is_running then
        return
    end
    self:next_tick(function()
        self.bdf_task_is_running = true
        local s
        while true do
            s = self.ncsi_config_obj:BDF({
                channel_id = self:get_port_id(), -- port id
                extra_cmd = self:get_port_id(), -- port id
                package_id = self.package_id
            }):value()
            if s and (s.bus ~= 0 or s.device ~= 0 or s.func ~= 0) and self.WorkloadType ~= 1 then
                self.BDF = string.format('%04x:%02x:%02x.%01x', 0, s.bus, s.device, s.func)
                self.t_prop_without_bma.BDF = self.BDF
                log:notice('update_BDF_by_ncsi %s bdf:%s', self.NodeId, self.BDF)
                break
            end
            log:debug('update_BDF_by_ncsi %s failed', self.NodeId)
            self:sleep_ms(60000)
        end
        self.bdf_task_is_running = false
    end)
end

function c_network_port:update_MAC_by_ncsi()
    local ncsi_failed_count = 0

    local parent = self:get_parent()
    local s
    if parent and (parent.Model == 'BroadCom' or parent.Model == 'BCM57414' or
        parent.Model == 'E810' or parent.Model == 'NetSwift') then
        s = self.ncsi_config_obj:MacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            package_id = self.package_id
        })
    else
        s = self.ncsi_config_obj:MacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            extra_cmd = self:get_port_id(), -- port id
            package_id = self.package_id
        })
    end
    s.on_data_change:on(function (data)
        if data then
            self.MACAddress = data
            log:notice('%s update mac address to %s by ncsi', self.NodeId, data)
            self.get_properties_res.MacAddressFromNcsi = true
            ncsi_failed_count = 0 -- 成功时重置计数

            -- NCSI恢复正常，停止备选SMBUS任务
            if self.smbus_schedulers.mac_address_task and not self.smbus_schedulers.mac_address_task.is_paused then
                self.smbus_schedulers.mac_address_task:pause()
                self.smbus_schedulers.mac_address_task.data = nil
                log:notice('%s MacAddress: NCSI recovered, stopped SMBUS fallback', self.NodeId)
            end
        end
    end)
    s.on_error:on(function()
        if self.get_properties_res.MacAddressFromNcsi then
            log:error('%s update mac address by ncsi on_error', self.NodeId)
            self.get_properties_res.MacAddressFromNcsi = false
        end

        -- NCSI失败达到阈值时，启动SMBUS备选
        ncsi_failed_count = ncsi_failed_count + 1
        if ncsi_failed_count >= NCSI_FAILED_THRESHOLD and self.smbus_schedulers.mac_address_task and
            self.smbus_schedulers.mac_address_task.is_paused then
            self.smbus_schedulers.mac_address_task:resume()
            log:notice('%s MacAddress: SMBUS fallback activated successfully', self.NodeId)
        end
    end)
    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update mac address by ncsi start', self.NodeId)
end

function c_network_port:update_default_MAC_by_ncsi()
    local parent = self:get_parent()
    local s
    if parent and (parent.Model == 'BroadCom' or parent.Model == 'BCM57414' or
        parent.Model == 'E810' or parent.Model == 'NetSwift') then
        s = self.ncsi_config_obj:DefaultMacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            package_id = self.package_id
        })
    else
        s = self.ncsi_config_obj:DefaultMacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            extra_cmd = self:get_port_id(), -- port id
            package_id = self.package_id
        })
    end
    s.on_data_change:on(function (data)
        if not data then
            return
        end
        self.PermanentMACAddress = data
        log:notice('%s update permanent mac address to %s by ncsi', self.NodeId, data)

        -- 判断工作mac地址是否有效，无效则用默认的永久地址进行刷新
        if self.MACAddress == MAC_ADDR_DEFAULT or self.MACAddress == INVALID_DATA_STRING then
            self.MACAddress = self.PermanentMACAddress
            log:notice('%s update mac address to %s by ncsi', self.NodeId, self.MACAddress)
        end
    end)
    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update default mac address by ncsi start', self.NodeId)
end

function c_network_port:update_DCBX_by_ncsi()
    local s = self.ncsi_config_obj:DCBX({
        channel_id = self:get_port_id(), -- port id
        package_id = self.package_id
    })
    s.on_data_change:on(function (data)
        for k, v in pairs(data) do
            self[k] = v
        end
        log:info('the dcbx info, up2cos:%s, up_pgid:%s, pgpct:%s, strict:%s, pfcmap:%s', self.Up2cos,
        self.Uppgid, self.Pgpct, self.PgStrict, self.Pfcmap)
    end)
    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update dcbx by ncsi start', self.NodeId)
end

local function set_lldp_for_hi182x(port, enable)
    local ok, ret
    if enable then
        -- 先尝试新命令字
        ok, ret = pcall(function()
            return port.ncsi_config_obj:SetOnLLDPTxEnable({channel_id = port:get_port_id(),
                package_id = port.package_id}):value()
        end)
        if not ok or not ret then
            -- 新命令字失败，则尝试旧命令字
            ok, ret = pcall(function()
                return port.ncsi_config_obj:SetOnLLDPTxEnableOld({channel_id = port:get_port_id(),
                    package_id = port.package_id}):value()
            end)
        end
        return ok, ret
    end
    -- 先尝试新命令字
    ok, ret = pcall(function()
        return port.ncsi_config_obj:SetOffLLDPTxEnable({channel_id = port:get_port_id(),
            package_id = port.package_id}):value()
    end)
    if not ok or not ret then
        -- 新命令字失败，则尝试旧命令字
        ok, ret = pcall(function()
            return port.ncsi_config_obj:SetOffLLDPTxEnableOld({channel_id = port:get_port_id(),
                package_id = port.package_id}):value()
        end)
    end
    return ok, ret
end

local function set_lldp_for_cx4(port, enable)
    local ok, ret
    if enable then
        ok, ret = pcall(function()
            return port.ncsi_config_obj:SetOnLLDPCapability({channel_id = port:get_port_id(),
                package_id = port.package_id}):value()
        end)
    else
        ok, ret = pcall(function()
            return port.ncsi_config_obj:SetOffLLDPCapability({channel_id = port:get_port_id(),
                package_id = port.package_id}):value()
        end)
    end
    -- Mellanox网卡需要重启网卡
    ok, ret = pcall(function()
        return port.ncsi_config_obj:ResetNic({package_id = port.package_id}):value()
    end)
    return ok, ret
end

function c_network_port:set_LLDP_capability_by_ncsi(enable, parent)
    local ok, ret
    if parent and string.find(parent.Model, 'Hi182') then
        ok, ret = set_lldp_for_hi182x(self, enable)
    elseif parent and parent.Model == 'CX4' then
        ok, ret = set_lldp_for_cx4(self, enable)
    end

    if not ok or not ret then
        log:error('set LLDP capability failed for port %s', self.NodeId)
    end
    return ok, ret
end

function c_network_port:set_LLDP_capability_by_mcu(enable)
    local ctx = context.get_context_or_default()
    local dpu_cards = client:GetDPUCardObjects()
    local position = self:get_position()
    local cur_dpu, ok, ret
    for _, dpu_card in pairs(dpu_cards) do
        if dpu_card.extra_params.Position == position then
            cur_dpu = dpu_card
            break
        end
    end
    if not cur_dpu then
        log:error("not find dpu card on position[%s]", position)
        return false, false
    end
    ok, ret = pcall(function ()
        return cur_dpu:SetLLDPStatus(ctx, self:get_port_id(), enable and 1 or 0)
    end)
    if not ok then
        log:error("unable to set LLDP capability for port %s, error: %s", self:get_port_id(), ret)
        return false, false
    end
    return true, true
end

-- 记录LLDP操作日志
function c_network_port:log_lldp_operation(enable, ctx, success)
    if ctx and common_func:has_initiator(ctx) then
        local operation_str = enable and 'Enable' or 'Disable'
        local result_str = success and 'successfully' or 'failed'
        log:operation(ctx:get_initiator(), 'NetworkAdapter',
            string.format('%s %s Port%s LLDP %s', operation_str,
                self.NetworkAdapterId, self.PortID, result_str))
    end
end

-- redfish调用此函数设置LLDP使能
function c_network_port:redfish_set_lldp_capability(enable)
    log:notice('set LLDP capability for port %s, enable: %s', self.NodeId, enable)
    local parent = self:get_parent()
    local ctx = context.get_context()
    -- 网卡不支持lldp使能
    if not parent.SupportedLLDP then
        log:error('not support set LLDP capability for port %s', self.NodeId)
        self:log_lldp_operation(enable, ctx, false)
        error(custom_messages.ConfigPortLLDPErr())
    end

    local ok, ret
    if self.WorkloadType == LOADED_NIC_OS then
        ok, ret = self:set_LLDP_capability_by_mcu(enable)
    elseif self.oem_smbus_config_obj and next(self.oem_smbus_config_obj) ~= nil then
        ok, ret = self.oem_smbus_config_obj:SetPortLLDPEnable(self, enable)
    else
        ok, ret = self:set_LLDP_capability_by_ncsi(enable, parent)
    end

    if not ok or not ret then
        log:error('set LLDP capability failed for port %s', self.NodeId)
        self:log_lldp_operation(enable, ctx, false)
        error(custom_messages.ConfigPortLLDPErr())
    end

    self:log_lldp_operation(enable, ctx, true)
    log:notice('%s port %s LLDP successfully', enable and 'enable' or 'disable', self.NodeId)
end

function c_network_port:update_LLDP_enabled_by_mcu()
    local ctx = context.get_context_or_default()
    local dpu_cards = client:GetDPUCardObjects()
    local position = self:get_position()
    local cur_dpu
    for _, dpu_card in pairs(dpu_cards) do
        if dpu_card.extra_params.Position == position then
            cur_dpu = dpu_card
            break
        end
    end
    if not cur_dpu then
        log:error("not find dpu card on position[%s]", position)
        return
    end
    local ok, status = pcall(function ()
        return cur_dpu:GetLLDPStatus(ctx, self:get_port_id())
    end)
    if not ok then
        log:error("unable to get LLDP capability for port %s, error: %s", self:get_port_id(), status)
        return
    end
    self.LLDPEnabled = status == 1 and true or false
end

function c_network_port:enable_lldp_over_mctp()
    self:next_tick(function()
        -- 最多尝试5次，每次间隔1分钟
        local max_retries = 5
        local retry_count = 0
        local retry_interval_ticks = 6000  -- 1分钟
        local ok, ret

        while retry_count < max_retries do
            retry_count = retry_count + 1
            ok, ret = pcall(function()
                return self.ncsi_config_obj:SetOnLLDPOverMCTPEnable({channel_id = self:get_port_id(),
                    package_id = self.package_id}):value()
            end)

            if ok and ret then
                log:notice('%s enable lldp over mctp successfully, retry_count: %d', self.NodeId, retry_count)
                return
            end

            if retry_count < max_retries then
                log:warn('enable lldp over mctp failed for port %s, retry %d/%d, will retry after 2 minutes',
                    self.NodeId, retry_count, max_retries)
                skynet.sleep(retry_interval_ticks)
            end
        end

        log:error('enable lldp over mctp failed for port %s after %d retries', self.NodeId, max_retries)
    end)
end

function c_network_port:update_lldp_tx_enabled_by_ncsi()
    local s
    local parent = self:get_parent()
    if parent and string.find(parent.Model, 'Hi182') then
        s = self.ncsi_config_obj:GetLLDPTxEnable({channel_id = self:get_port_id(), package_id = self.package_id})
    elseif parent and parent.Model == 'CX4' then
        s = self.ncsi_config_obj:GetLLDPCapability({channel_id = self:get_port_id(), package_id = self.package_id})
    end
    self:connect_signal(s.on_data_change, function(data)
        self.LLDPEnabled = data
        log:notice('%s update lldp tx enabled to %s by ncsi', self.NodeId, data)
    end)
    table.insert(self.schedulers, s)
    s:start()
    log:notice('%s update lldp tx enabled by ncsi start', self.NodeId)
end

function c_network_port:set_package_id(package_id)
    self.package_id = package_id
end

function c_network_port:update_ncsi_properties()
    self:next_tick(self.update_ncsi_port_stats, self)
    self:next_tick(self.update_ncsi_link_status, self)
    self:next_tick(self.update_BDF_by_ncsi, self)
    self:next_tick(self.update_MAC_by_ncsi, self)
    self:next_tick(self.update_DCBX_by_ncsi, self)
    self:next_tick(self.update_default_MAC_by_ncsi, self)
    if self.WorkloadType == LOADED_NIC_OS then
        self:next_tick(self.update_LLDP_enabled_by_mcu, self)
    else
        self:next_tick(self.update_lldp_tx_enabled_by_ncsi, self)
    end
    local op = self:get_optical_module()
    if op then
        op:update_ncsi_properties(self.ncsi_config_obj, self.package_id)
    end
end

function c_network_port:get_optical_module()
    return c_optical_module.collection:find({
        PortID = self.PortID,
        NetworkAdapterId = self.NetworkAdapterId
    })
end

function c_network_port:update_link_status(link_status)
    local op = self:get_optical_module()
    if not op then
        return
    end

    local fault_state = op:get_fault_state()
    op:set_link_status(link_status)
    local power_state_flag = (((fault_state & 771) ~= 0) and (link_status ~= 'LinkDown') and
                                 (link_status ~= INVALID_DATA_STRING))
    op:set_power_state((power_state_flag and 1 or 0))
end

function c_network_port:synchronize_link_status(link_status)
    local status = (link_status == 1 and 'Connected') or (link_status == 0 and 'Disconnected') or 'N/A'
    self:set_link_status(status)
end

function c_network_port:update_ipv6_default_gateway(ipv6_addresses)
    local first_ipv6_gateway = ''
    for _, addr in ipairs(ipv6_addresses) do

        -- 获取第一个ipv6 gateway，它就是default_ipv6_gateway
        if #addr.Gateway > 0 and first_ipv6_gateway == '' then
            first_ipv6_gateway = addr.Gateway[1]
        end
    end
    self.IPv6DefaultGateway = first_ipv6_gateway
end

function c_network_port:reset_bma_info()
    log:notice('%sPort%s reset bma info', self.NetworkAdapterId, self.PortID)
    self.OSLinkStatus = ''
    self.Name = ''
    self.WorkMode = 'NonLoop'
    self.DriverName = ''
    self.DriverVersion = ''

    if self.PermanentMACAddress and self.PermanentMACAddress ~= MAC_ADDR_DEFAULT and
        self.PermanentMACAddress ~= INVALID_DATA_STRING then
        log:notice('NetworkPort(%sPort%s) macaddr reset from permanent mac(%s) complete',
            self.NetworkAdapterId, self.PortID, self.PermanentMACAddress)
        self.MACAddress = self.PermanentMACAddress
    end
    -- FC卡相关属性
    if self.FCId and #self.FCId > 0 then
        self.WWPN = ''
        self.WWNN = ''
        self.SpeedGbps = 0
        self.FCId = ''
        self.FirmwareVersion = ''
    end
    -- 恢复非bma途径获取的属性
    if self.CreatedByDeviceObject then
        local bus = c_object_manage.get_instance().bus
        local device_obj = device_comm_fun.get_device_obj_by_path_interface(bus, self.device_path,
            device_comm_defs.NETWORK_PORT_OEM_INTERFACE)
        if not device_obj then
            log:notice('%sPort%s reset_bma_info failed', self.NetworkAdapterId, self.PortID)
            return
        end
        local ok = pcall(function()
            return device_obj:call_method('RestorePropAfterBmaStop', '')
        end)
        if not ok then
            log:notice('%sPort%s reset_bma_info failed', self.NetworkAdapterId, self.PortID)
            return
        end
    else
        self:restore_firmware_version_after_bma_stop()
    end
    log:notice('%sPort%s reset bma info complete', self.NetworkAdapterId, self.PortID)
end

function c_network_port:reset_bma_stats()
    if not next(self.ncsi_config_obj) or self.ncsi_config_obj.PortMetrics then
        self.RXFrames = ''
        self.TXFrames = ''
    end
    self.BandwidthUsagePercent = 0
    log:debug('NetworkPort(%sPort%s) stats reset from bma complete', self.NetworkAdapterId,
        self.PortID)
end

function c_network_port:emit_mac_change_signal(value)
    self.mac_change:emit()
    log:notice('NetworkPort(%sPort%s) PermanentMACAddress change signal emit, new mac is %s',
        self.NetworkAdapterId, self.PortID, value)
end

function c_network_port:emit_actual_mac_change_signal(value)
    pcall(function()
        log_netcard_info.actual_mac_change_sig:emit()
    end)
end

local property_changed_switch<const> = {
    LinkStatus = c_network_port.update_link_status,
    LinkStatusNumeric = c_network_port.synchronize_link_status,
    PermanentMACAddress = c_network_port.emit_mac_change_signal,
    MACAddress = c_network_port.emit_actual_mac_change_signal
}

function c_network_port:register_property_changed_callback()
    local link_status = self.LinkStatusNumeric == 1 and 'Connected' or
        self.LinkStatusNumeric == 0 and 'Disconnected' or INVALID_DATA_STRING
    self:set_link_status(link_status)
    self:connect_signal(self.on_property_changed, function(name, value)
        if property_changed_switch[name] then
            self:next_tick(property_changed_switch[name], self, value)
        end
    end)

    if self['bmc.kepler.Systems.NetworkPort'] then
        self:connect_signal(self['bmc.kepler.Systems.NetworkPort'].property_before_change, function(name, value)
            if name == 'LLDPEnabled' then
                self:redfish_set_lldp_capability(value)
            end
        end)
    end
end

function c_network_port:start_npu_link_status_listening()
    self:connect_signal(self.on_property_changed, function(name, value)
        if name ~= 'LinkStatusValue' then
            return
        end
        self.LinkStatus = value == 1 and 'Connected' or 'Disconnected'
        -- 判断上下电状态和复位信号，非上电状态或者复位时，不更新 numberic
        -- 网口down告警需要依赖NPU驱动状态正常（self.PowerOn == 1表示NPU驱动正常加载），在模组驱动非正常加载的情况下，需屏蔽网口down告警。
        if fructl.get_power_status() ~= 'ON' or fructl.get_system_reset_flag() == 1 or (value == 0 and self.PowerOn ~= 1) then
            return
        end
        -- 使用LinkStatusNumeric表示告警状态：初始值（255） -> down(2) -> up(1) -> down(0)
        -- 出现down，LinkStatusNumeric被更新为2 或者 0
        -- 出现up, LinkStatusNumeric被更新为1
        if value == 0 then
            if self.LinkStatusNumeric == 255 then
                self.LinkStatusNumeric = 2
                log:notice("npu port%s first link down", self.PortID)
                return
            end
            if self.LinkStatusNumeric == 1 then
                self.LinkStatusNumeric = 0
                log:notice("npu port%s link down", self.PortID)
            end
        end
        if value == 1 then
            self.LinkStatusNumeric = 1
            log:notice("npu port%s link up", self.PortID)
        end
    end)
end

function c_network_port:register_npu_changed_callback()
    self:start_npu_link_status_listening()
    self.LinkStatus = self.LinkStatusValue == 1 and 'Connected' or 'Disconnected'
    -- 启动监听后，如果是正常上电状态，先同步一次属性值，防止组件异常复位后，无信号变更触发，导致资源树值为初始值，与实际硬件值不同。
    if fructl.get_power_status() == 'ON' and fructl.get_system_reset_flag()  == 0 then
        if self.LinkStatusValue == 1 then
            self.LinkStatusNumeric = 1
        else
            self.LinkStatusNumeric = 2
        end
        log:notice("npu port%s link status is to %s", self.PortID, self.LinkStatus)
    end
end

--MPU恢复空闲则发送一次性信息获取指令
function c_network_port:resume_fetch_properties()
    self:next_tick(self.update_BDF_by_ncsi, self)
end

-- MPU繁忙时暂停所有的轮询操作
function c_network_port:pause()
    log:notice("pause port schedulers ,size of port schedulers is %s", #self.schedulers)
    for _, s in ipairs(self.schedulers) do
        s:pause()
    end
    -- 暂停对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:pause()
    end
end

-- MPU空闲时恢复所有的轮询操作
function c_network_port:resume()
    log:notice("resume port schedulers ,size of port schedulers is %s", #self.schedulers)
    self:resume_fetch_properties()
    for _, s in ipairs(self.schedulers) do
        s:resume()
    end
    -- 恢复对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:resume()
    end
end

-- 卸载时停止所有的轮询操作
function c_network_port:stop()
    for _, s in ipairs(self.schedulers) do
        s:deconstruct()
    end
    self.schedulers = {}
    -- 停止对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:stop()
    end
end

local old_dtor = c_network_port.dtor
function c_network_port:dtor()
    self:stop()
    -- 网口对象卸载时，需要把其子路径上VLANs及ipv4、ipv6均卸载
    local vlans = c_vlan.collection:fetch({PortID = self.PortID, NetworkAdapterId = self.NetworkAdapterId})
    for _, vlan in ipairs(vlans) do
        vlan:reset()
        c_vlan.collection:del_object(vlan)
    end

    class_mgnt('VLANs'):remove(self.vlans)

    -- 卸载时确保数据库不操作
    local db = c_object_manage.get_instance().per_db
    db.db:flush()
end

function c_network_port:destroy()
    self:dtor()
    old_dtor(self)
end

function c_network_port:ctor()
    self.schedulers = {}
    self.lldp_config_obj = {}
    self.ncsi_config_obj = {}
    self.smbus_schedulers = {}
    self.oem_smbus_config_obj = {}
    self.package_id = 0
    self.na_object_name = ''
    self.np_object_name = ''
    self.lldp_clear_task = false
    self.tasks = c_tasks.new()
    self.vlans = false
    self.get_ncsi_link_status_succeess = false
    self.npu_ipv4_info = {}
    self.npu_ipv4_obj = false
    -- 此表用于临时保存非bma获取的属性，用于bma断开后恢复，当前仅LinkStatus使用
    self.t_prop_without_bma = {}
    self.ncsi_get_linkstatus_retry_count = 0
    self.is_npu_heartbeat_loss = false
    self.is_dmp_status_ready = false
    self.mac_change = signal.new()
    self.record_flag = 0
    self.hccs_flag = 2
    self.link_up_cnt = 0
    self.link_down_cnt = 0
    self.link_up_timestamp = {}
    self.link_down_timestamp = {}
    self.per_module_log_cnt = 0
    self.npu_port_log_buffer = {}
    self.npu_port_cdr_info_abnormal = false
    self.children = {}
    self.parent = {}
    self.device_path = ''
    self.hccs_info = {
        npu_id = 0,
        health_status = 0,
        lane_mode = {},
        link_lane_list = {},
        link_speed = {},
        tx_packets = {},
        tx_bytes = {},
        rx_packets = {},
        rx_bytes = {},
        retry_count = {},
        error_count = {},
        first_error_lane = 255,
        snr_max = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0},
            {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
        snr_min = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0},
            {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
        half_height_max = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0},
            {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
        half_height_min = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0},
            {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}
    }
    self.get_properties_res = {}
    self.bdf_task_is_running = false
end

local function is_virtual_port(type)
    if type == BUSINESSPORT_CARD_TYPE_VIR or
       type == BUSINESSPORT_CARD_TYPE_BRIDGE or
       type == BUSINESSPORT_CARD_TYPE_TEAM then
        return true
    end
    return false
end

function c_network_port:update_mac_addr_from_db()
    local card = self:get_parent()
    -- 不支持热插拔的网卡无需从数据库中恢复mac地址
    if not card or not card.HotPluggable then
        return
    end

    local db = c_object_manage.get_instance().per_db
    local network_port = db:select(db.NetworkPort):where({PortID = self.PortID,
        NetworkAdapterId = self.NetworkAdapterId}):first()
    local times = 0

    while not network_port and times < 20 do
        network_port = db:select(db.NetworkPort):where({PortID = self.PortID,
            NetworkAdapterId = self.NetworkAdapterId}):first()
        self:sleep_ms(1000)
        times = times + 1
    end

    if network_port then
        log:notice('set macaddr from database, mac = %s, port = %s', network_port.MACAddress, self.PortID)
        self.MACAddress = network_port.MACAddress
        self.PermanentMACAddress = network_port.MACAddress
    else
        log:notice('can not find macaddr from database, port = %s', self.PortID)
    end
end

function c_network_port:init_id()
    local retry_times = 0

    -- bridge 不通过devicelocator生成ID信息
    if is_virtual_port(self.Type) then
        return
    end
    local parent = self:get_parent()
    while not parent or parent.NodeId == '' and retry_times < 10 do
        retry_times = retry_times + 1
        self.tasks:sleep_ms(1000)
    end
    self.NetworkAdapterId = parent.NodeId
    self.NodeId =  parent.NodeId .. (self.PortID + 1)
    self.t_prop_without_bma = {
        LinkStatus = INVALID_DATA_STRING
    }
    self:register_mdb_objects()
end

function c_network_port:get_npu_cdr_temp_from_imu_retry()
    local result = true
    local temp = 0
    local cdr_manu = ""
    local ok
    local parent = self:get_parent()
    for i = 1, 3 do -- 最多尝试3次
        ok, result, temp, cdr_manu = pcall(npu_imu_cmd.get_npu_cdr_temp_from_imu, self.NpuID, self.PowerOn,
                self.UdieId or 0, self.PhysicalId or 0, self.PfId or 0, parent.Model)
        if ok and result then
            return ok, result, temp, cdr_manu
        end
        skynet.sleep(10) -- 延时100ms
    end
    return ok, result, temp, cdr_manu
end

function c_network_port:get_npu_cdr_temp_from_imu()
    local temp = 0
    local cdr_manu = ""
    local ok, result
    if fructl.get_power_status() ~= 'ON' or self.PowerOn ~= 1 then
        log:debug('[NPU] npu is power off')
        self.npu_port_cdr_info_abnormal = false
        return temp, cdr_manu
    end
    if self.is_npu_heartbeat_loss then
        log:debug('[NPU] npu %s heartbeat loss, pause get npu port info', self.NpuID)
        self.npu_port_cdr_info_abnormal = true
        return temp
    end
    ok, result, temp, cdr_manu = self:get_npu_cdr_temp_from_imu_retry()
    if not ok then
        log:debug('[NPU] npu %s get cdr temp fail, err: %s', self.NpuID, result)
        return temp, cdr_manu
    end
    if result or not self.is_dmp_status_ready then
        self.npu_port_cdr_info_abnormal = false
    else
        self.npu_port_cdr_info_abnormal = true
    end
    log:debug('[NPU] npu %s cdr temp is %s', self.NpuID, temp)
    return temp, cdr_manu
end

function c_network_port:get_optical_module_with_extra()
    local op
    c_optical_module.collection:fold(function(_, obj)
        for _, v in pairs(obj.RelatedNetworkPorts) do
            if v == self.PortID then
                op = obj
                return
            end
        end
    end)
    return op
end

local function check_port_power_milliwatts(op)
    -- 光模块不一定最开始就加载了
    if not op or not op.RXInputPowerMilliWatts or next(op.RXInputPowerMilliWatts) == nil then
        return true
    end
    for _, v in pairs(op.RXInputPowerMilliWatts) do
        -- 有一个通道收光功率大于0.01W,则认为光模块收光
        if v > RX_POWER_LOWER_THRETHOLD and v ~= 65535 then
            return true
        end
    end
    return false
end

function c_network_port:check_port_link_status(start_time, last_link_tatus_value)
    local op = self:get_optical_module_with_extra()
    -- port未检测到收光，则不告警
    if not check_port_power_milliwatts(op) then
        self.LinkDown = 0
        return
    end
    -- 曾经连接过后断开、或者持续断开一段时间，产生告警
    if (last_link_tatus_value == 1 and self.LinkStatusValue == 0) or
        (self.MediumType == "FiberOptic" and vos.vos_tick_get() - start_time > LINK_DOWN_ALARM_TIME) then
        self.LinkDown = 1
    end
end

function c_network_port:npu_driver_monitor()
    local last_power_on = self.PowerOn
    local last_link_tatus_value = self.LinkStatusValue
    local start_time = 0
    while true do
        -- 驱动未加载或者已链接，均置位LinkDown以恢复告警，无需后续检测
        if self.PowerOn ~= 1 or self.LinkStatusValue == 1 then
            self.LinkDown = 0
            goto continue
        end
        -- 驱动首次加载时重置时间戳
        if last_power_on ~= 1 and self.PowerOn == 1 then
            start_time = vos.vos_tick_get()
        end
        -- 只在开始的一个时间段内检测
        if vos.vos_tick_get() - start_time < LINK_STATUS_CHECK_TIME then
            self:check_port_link_status(start_time, last_link_tatus_value)
        end
        ::continue::
        last_power_on = self.PowerOn
        last_link_tatus_value = self.LinkStatusValue
        self:sleep_ms(1000)
    end
end

function c_network_port:init()
    if self.CreatedByDeviceObject then
        log:notice('c_network_port init start, NetworkAdapterId(%s), PortID(%s)', self.NetworkAdapterId, self.PortID)
        c_network_port.super.init(self)
        return
    end
    self:init_id()
    c_network_port.super.init(self)
    local parent = self:get_parent()
    if parent and string.find(parent.NodeId, 'ROH') then
        return
    end
    -- 网口必须依赖其父对象网卡上树
    if parent and parent.Model and string.match(parent.Model, 'NPU') then
        log:error('[npuport]port model = %s', parent.Model)
        log:error('NPU%s port init start', parent.SlotNumber)
        self:next_tick(function ()
            self:update_npu_heartbeat_status()
            self:get_info_from_imu()
            self:npu_driver_monitor()
        end)
        -- npu虚拟网卡开启网口状态监控
        self:connect_signal(self.on_add_object_complete, function()
            self:register_npu_changed_callback()
        end)
    else
        self.na_object_name, self.np_object_name = self.path:match('([^/]+)/Ports/([^/]+)')
        self.vlans = c_object_manage.get_instance().app:CreateVLANs(1, self.na_object_name, self.np_object_name,
            function (obj)
                obj.NetworkPortObjectName = self.na_object_name .. self.np_object_name
                local mdb_object = obj:get_mdb_object('bmc.kepler.Systems.NetworkPort.VLANs')
                rawset(mdb_object, 'get_parent', function ()
                return self
            end)
        end)

        self:connect_signal(self.on_add_object_complete, function()
            self:register_property_changed_callback()
        end)
        self:next_tick(self.update_mac_addr_from_db, self)
    end
    self:update_processor_ports()
end

-- 一个网口关联两个光模块,一个光模块关联四个网口，遍历所有相关的光模块，只要一个在位就认为没有丢失心跳
-- 网口:光模块=2:1，有一半网口没有port id和光模块对应，如果只是按照port id匹配会漏掉一半网口的心跳
function c_network_port:update_npu_heartbeat_status_by_presence()
    local op_table = {}
    c_optical_module.collection:fold(function(_, obj)
        for _, v in pairs(obj.RelatedNetworkPorts) do
            if v == self.PortID then
                op_table[#op_table + 1] = obj
                return
            end
        end
    end)
    -- 由于是并行任务，共享is_npu_heartbeat_loss，避免先修改is_npu_heartbeat_loss导致任务出错
    local heartbeat_loss_flag = true
    for _, op in pairs (op_table) do
        if op.Presence == 1 then
            heartbeat_loss_flag = false
            op:set_npu_heartbeat_loss_status(false)
        else
            op:set_npu_heartbeat_loss_status(true)
        end
    end
    if heartbeat_loss_flag then
        self.is_npu_heartbeat_loss = true
    else
        self.is_npu_heartbeat_loss = false
    end
end

function c_network_port:update_npu_heartbeat_status()
    log:notice('[NPU] update_npu_heartbeat_status task start, id = %s', self.NpuID)
    local op
    local npu_power_on = self.PowerOn
    local heartbeat_ret
    local heartbeat_try_times = 3 --NPU 驱动加载完成后，如果通道返回3，则重试3
    local model = self:get_parent().Model
    skynet.fork_loop({count = 0}, function()
        while true do
            self:sleep_ms(15000)
            if fructl.get_power_status() ~= 'ON' or self.PowerOn ~= 1 then
                npu_power_on = self.PowerOn
                heartbeat_try_times = 3
                log:debug('[NPU] npu is power off')
                goto next
            end
            if is_support_ub(model) then
                self:update_npu_heartbeat_status_by_presence()
                heartbeat_ret = 0
            else
                self.is_npu_heartbeat_loss, heartbeat_ret = npu_imu_cmd.heartbeat_check(self.NpuID, self.UdieId or 0,
                        self.PhysicalId or 0, self.PfId or 0, model)
            end
            op = self:get_optical_module()
            if op and not is_support_ub(model) then
                op:set_npu_heartbeat_loss_status(self.is_npu_heartbeat_loss)
            end
            --NPU 驱动加载完成后，如果通道返回3，则重试3
            if heartbeat_ret == 3 and npu_power_on ~= 1 and heartbeat_try_times > 0 then
                heartbeat_try_times = heartbeat_try_times - 1
                self.is_dmp_status_ready = false
                goto next
            end
            self.is_dmp_status_ready = true
            npu_power_on = self.PowerOn
            heartbeat_try_times = 0
            ::next::
            if op then
                op:set_dmp_ready_status(self.is_dmp_status_ready)
            end
        end
    end)
end

function c_network_port:clear_npu_port_basic_info()
    self.RXFrames = ''
    self.TXFrames = ''
    self.PacketsDropped = 0
    self.MACAddress = MAC_ADDR_DEFAULT
    self.RXFCSErrors = ''
    if self.npu_ipv4_obj then
        class_mgnt('IPv4Address'):remove(self.npu_ipv4_obj)
        self.npu_ipv4_info = {
            Address = INVALID_DATA_STRING,
            SubnetMask = INVALID_DATA_STRING,
            Gateway = INVALID_DATA_STRING
        }
    end
    log:debug('[NPU] clear npu %s port basic info success', self.NpuID)
end

function c_network_port:npu_port_update_buffer(tmp_buffer)
    if self.per_module_log_cnt >= NPU_PORT_PER_HOUR_PER_MODULE_LOG_NUM then
        log:debug('[NPU] Port%s: Limit the log number', self.PortID)
        return
    end
    self.npu_port_log_buffer[#self.npu_port_log_buffer + 1] = tmp_buffer
    self.per_module_log_cnt = self.per_module_log_cnt + 1
end

function c_network_port: npu_port_get_diff_timestamp(new_timestamp, old_timestamp)
    local out_timestamp = {}
    table.sort(new_timestamp, function(a, b)
        return a > b
    end)
    table.sort(old_timestamp, function(a, b)
        return a > b
    end)
    for i = 1, NPU_PORT_TIMESTAMP_MAX_NUM, 1 do
        if old_timestamp[1] and new_timestamp[i] and
            new_timestamp[i] <= old_timestamp[1] or new_timestamp[i] == 0 then
            break
        end
        if #out_timestamp >= NPU_PORT_TIMESTAMP_MAX_NUM then
            break
        end
        out_timestamp[#out_timestamp + 1] = new_timestamp[i]
    end
    return out_timestamp
end

function c_network_port:npu_port_link_event(info, is_link_down)
    if not info then
        return
    end
    local old_cnt_ptr = is_link_down and self.link_down_cnt or self.link_up_cnt
    local new_cnt_ptr = is_link_down and info.link_down_cnt or info.link_up_cnt
    local old_timestamp = is_link_down and self.link_down_timestamp or self.link_up_timestamp
    local new_timestamp = is_link_down and info.link_down_timestamp or info.link_up_timestamp
    local normal_type = is_link_down and PORT_LINK_DOWN or PORT_LINK_UP
    local notice_type = is_link_down and PORT_LINK_DOWN_NOTICE or PORT_LINK_UP_NOTICE
    local cnt_diff = new_cnt_ptr - old_cnt_ptr
    if cnt_diff == 0 then
        return --计数一致，说明没有事件产生
    elseif cnt_diff < 0 or old_cnt_ptr == 0 then
        if is_link_down then
            self.link_down_cnt = new_cnt_ptr
        else
            self.link_up_cnt = new_cnt_ptr
        end
        return -- 系统首次启动或者出现OS/NPU复位场景，同步查询记录到本地，不更新到buffer
    end
    -- 计算存在差异的时间戳
    local diff_timestamp = self:npu_port_get_diff_timestamp(new_timestamp, old_timestamp)
    for i = 1, #diff_timestamp, 1 do
        local tmp_buffer = {
            timestamp = 0,
            npu_id = self.NpuID,
            type = normal_type
        }
        tmp_buffer.timestamp = diff_timestamp[i]
        if (cnt_diff > NPU_PORT_TIMESTAMP_MAX_NUM / 2) and i == #diff_timestamp then
            tmp_buffer.type = notice_type
        end
        self:npu_port_update_buffer(tmp_buffer)
    end
end

function c_network_port:npu_port_log_collect_record(info)
    self:npu_port_link_event(info, true)
    self:npu_port_link_event(info, false)
    self.record_flag = self.record_flag + 1
end

function c_network_port:get_npu_port_log_buffer()
    return self.npu_port_log_buffer
end

function c_network_port:clear_npu_port_log_buffer()
    self.npu_port_log_buffer = {}
end

function c_network_port:get_npu_port_hccs_record()
    return self.hccs_info
end

function c_network_port:clear_hccs_info()
    self.hccs_info.npu_id = self.NpuID
    self.hccs_info.health_status = 0
    self.hccs_info.lane_mode = {}
    self.hccs_info.link_lane_list = {}
    self.hccs_info.link_speed = {}
    self.hccs_info.tx_packets = {}
    self.hccs_info.tx_bytes = {}
    self.hccs_info.rx_packets = {}
    self.hccs_info.rx_bytes = {}
    self.hccs_info.retry_count = {}
    self.hccs_info.error_count = {}
    self.hccs_info.first_error_lane = 255
end

function c_network_port:update_npu_port_hccs_info(info)
    self:clear_hccs_info()
    local index
    for _, macro_info in ipairs(info) do
        index = macro_info.macro_id
        if macro_info.health_status and
            macro_info.health_status > self.hccs_info.health_status then
            self.hccs_info.health_status = macro_info.health_status
        end
        self.hccs_info.lane_mode[index] = macro_info.lane_mode or 0
        self.hccs_info.link_lane_list[index] = macro_info.link_lane_list or 0
        self.hccs_info.link_speed[index] = macro_info.link_speed or 0
        self.hccs_info.tx_packets[index] = macro_info.tx_packets or 0
        self.hccs_info.tx_bytes[index] = macro_info.tx_bytes or 0
        self.hccs_info.rx_packets[index] = macro_info.rx_packets or 0
        self.hccs_info.rx_bytes[index] = macro_info.rx_bytes or 0
        self.hccs_info.retry_count[index] = macro_info.retry_count or 0
        self.hccs_info.error_count[index] = macro_info.error_count or 0
        if macro_info.first_error_lane and
            macro_info.first_error_lane < self.hccs_info.first_error_lane then
            self.hccs_info.first_error_lane = macro_info.first_error_lane
        end
        for j = 1, LANE_PER_MACRO, 1 do
            if macro_info.snr[j] > self.hccs_info.snr_max[index][j] then
                self.hccs_info.snr_max[index][j] = macro_info.snr[j]
            end
            if macro_info.snr[j] < self.hccs_info.snr_min[index][j] or
                self.hccs_info.snr_min[index][j] == 0 then
                self.hccs_info.snr_min[index][j] = macro_info.snr[j]
            end
            if macro_info.half_height[j] > self.hccs_info.half_height_max[index][j] then
                self.hccs_info.half_height_max[index][j] = macro_info.half_height[j]
            end
            if macro_info.half_height[j] < self.hccs_info.half_height_min[index][j] or
                self.hccs_info.half_height_min[index][j] == 0 then
                self.hccs_info.half_height_min[index][j] = macro_info.half_height[j]
            end
        end
    end
end

function c_network_port:update_npu_port_basic_info(info)
    -- 更新mac和统计信息
    self.RXFrames = info.received
    self.TXFrames = info.transmitted
    self.PacketsDropped = info.dropped
    self.MACAddress = info.mac_addr
    self.RXFCSErrors = info.rx_fcs_err_pkt_num
    log:info('[NPU] npu%s ipv4 =%s, gateway= %s, netmask =%s',
        self.NpuID, info.ip_addr, info.gateway, info.subnet_mask)
end

function c_network_port:get_npu_port_hccs_info()
    local info
    local parent = self:get_parent()
    info = npu_imu_cmd.get_hccs_info_from_imu(self.NpuID, self.PowerOn, self.UdieId or 0, self.PhysicalId or 0,
                    self.PfId or 0, parent.Model)
    if #info > 0 then
        self:update_npu_port_hccs_info(info)
    end
    self.hccs_flag = 0
end

function c_network_port:get_npu_port_basic_info()
    local app, ip_addr, info, adapter_object_name, port_object_name, ipv4
    app = c_object_manage.get_instance().app
    local parent = self:get_parent()
    info = npu_imu_cmd.get_info_from_imu(self.NpuID, self.PowerOn, self.UdieId or 0, self.PhysicalId or 0,
                    self.PfId or 0, parent.Model)
    self:update_npu_port_basic_info(info)
    pcall(function()
        self:npu_port_log_collect_record(info)
    end)
    -- npu仅能获取一个ipv4信息，当ip网关掩码均不变时不做资源树对象操作
    if self.npu_ipv4_obj and self.npu_ipv4_info.Address == info.ip_addr and
        self.npu_ipv4_info.SubnetMask == info.subnet_mask and
        self.npu_ipv4_info.Gateway == info.gateway then
        log:info('[NPU] npu%s ipv4 info not change', self.NpuID)
        return
    end

    -- 非首次上树ipv4对象时，需要先下树后再上树
    if self.npu_ipv4_obj then
        class_mgnt('IPv4Address'):remove(self.npu_ipv4_obj)
        self.npu_ipv4_info = { Address = INVALID_DATA_STRING, SubnetMask = INVALID_DATA_STRING,
            Gateway = INVALID_DATA_STRING }
        log:info('[NPU] clear npu%s ipv4 info success', self.NpuID)
    end

    -- 当ip网关掩码均为无效值时不上树
    if info.ip_addr == INVALID_DATA_STRING and info.subnet_mask == INVALID_DATA_STRING and
        info.gateway == INVALID_DATA_STRING then
        log:info('[NPU] npu%s ipv4 info is invalid', self.NpuID)
        return
    end

    -- 更新ipv4缓存信息并上树
    adapter_object_name = string.format('NetworkAdapter_1_%s', self:get_position())
    port_object_name = string.format('NetworkPort_%02d_%s', (self.PortID + 1), self:get_position())
    self.npu_ipv4_info.Address = info.ip_addr
    self.npu_ipv4_info.SubnetMask = info.subnet_mask
    self.npu_ipv4_info.Gateway = info.gateway
    self.npu_ipv4_obj = app:CreateIPv4Address(1, adapter_object_name, port_object_name,
        str_hash(info.ip_addr .. info.subnet_mask .. info.gateway), function(obj)
            obj.Address = info.ip_addr
            obj.SubnetMask = info.subnet_mask
            obj.Gateway = info.gateway
            obj.NetworkAdapterObjectName = adapter_object_name
            obj.NetworkPortObjectName = port_object_name
            obj.PortID = self.PortID
            obj.NetworkAdapterId = self.NetworkAdapterId
        end)
    log:info('[NPU] npu%s update ipv4 info success', self.NpuID)
end

function c_network_port:get_npu_port_info()
    -- 与hdk通信失败或下电时，清空已有信息
    if self.is_npu_heartbeat_loss or fructl.get_power_status() ~= 'ON' or self.PowerOn ~= 1 then
        log:debug('[NPU] npu %s heartbeat loss, pause get npu port basic info', self.NpuID)
        self:clear_npu_port_basic_info()
        return
    end

    local ok, err = pcall(function()
        self:get_npu_port_basic_info()
    end)
    if not ok then
        log:error('[NPU] get npu%s port basic info from imu fail, err: %s', self.NpuID, err)
    end
    
    ok, err = pcall(function()
        if self.hccs_flag >= 2 then
            self:get_npu_port_hccs_info()
        else
            self.hccs_flag = self.hccs_flag + 1
        end
    end)
    if not ok then
        log:error('[NPU] get npu%s port hccs info from imu fail, err: %s', self.NpuID, err)
    end
    -- 整个轮询周期为600秒
    self:sleep_ms(600000 - 60000)
    if self.record_flag % 6 == 0 then
        self.per_module_log_cnt = 0
    end
end

function c_network_port:get_info_from_imu()
    self.npu_ipv4_info = {
        Address = INVALID_DATA_STRING,
        SubnetMask = INVALID_DATA_STRING,
        Gateway = INVALID_DATA_STRING
    }
    skynet.fork_loop({count = 0}, function()
        local ok, err
        while true do
            self:get_npu_port_info()
            self:sleep_ms(60000)
        end
    end)
end

local function generate_object_name(type, port_id)
    if type == BUSINESSPORT_CARD_TYPE_VIR then
        return "VirtaulPort_" .. (port_id + 1)
    end
    if type == BUSINESSPORT_CARD_TYPE_BRIDGE then
        return "BridgePort_" .. (port_id + 1)
    end
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        return "TeamPort_" .. (port_id + 1)
    end
    return "Port_" .. (port_id + 1)
end

function c_network_port.create_mdb_object(value, card_orm_obj)
    local app = c_object_manage.get_instance().app
    if value.CreatedByDeviceObject then
        return app:CreateNetworkPort(1, value.CardObjectName, value.ObjectName, function (obj)
            log:notice("create network port mdb object, NetworkAdapterId(%s), PortID(%s)",
                value.NetworkAdapterId, value.PortID)
            obj.PortID = value.PortID
            obj.NetworkAdapterId = value.NetworkAdapterId
            obj.ObjectName = value.ObjectName
            obj.ObjectIdentifier = value.ObjectIdentifier
            obj.CreatedByDeviceObject = true

            rawset(obj, 'get_parent', function()
                return card_orm_obj
            end)
        end)
    end
    local obj_name = generate_object_name(value.Type, value.PortID)
    return app:CreateNetworkPort(1, value.NetworkAdapterObjectName, obj_name, function (obj)
        log:debug("create network port id(%d), nodeid(%s), type(%d), BDF(%s)",
            value.PortID, value.NodeId, value.Type, value.BDF)
        obj.PortID = value.PortID
        obj.NodeId = value.NodeId
        obj.NetworkAdapterId = value.NetworkAdapterId
        obj.Type = value.Type
        obj.BDF = value.BDF
        obj.NetworkAdapterObjectName = value.NetworkAdapterObjectName
    end)
end

function c_network_port.insert_or_update(na_obj_name, port)
    local db_addr = c_network_port.collection:find(port)
    if db_addr then
        port = db_addr
    end
    port.NetworkAdapterObjectName = na_obj_name
    return db_addr or c_network_port.__table(port)
end

function c_network_port:update_processor_ports()
    local npu_num = tonumber(self.NpuID)
    if npu_num and self.ProcessorPorts[1] and self.ProcessorPorts[1][1] == '' then
        local processor_ports = {}
        local silk_num = string.format('NPU%s', self.NpuID)
        for i = 1, #self.ProcessorPorts do
            processor_ports[i] = {silk_num, self.ProcessorPorts[i][2]}
        end
        self.ProcessorPorts = processor_ports
    end
end

return c_network_port
