2019-11-06 20:56:17 -06:00
|
|
|
#############################################################################
|
|
|
|
# Celestica
|
|
|
|
#
|
|
|
|
# Module contains an implementation of SONiC Platform Base API and
|
|
|
|
# provides the fan status which are available in the platform
|
|
|
|
#
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
import json
|
|
|
|
import math
|
|
|
|
import os.path
|
|
|
|
|
|
|
|
try:
|
|
|
|
from sonic_platform_base.fan_base import FanBase
|
2020-11-25 12:28:36 -06:00
|
|
|
from .helper import APIHelper
|
2019-11-06 20:56:17 -06:00
|
|
|
except ImportError as e:
|
|
|
|
raise ImportError(str(e) + "- required module not found")
|
|
|
|
|
|
|
|
|
|
|
|
FAN_NAME_LIST = ["FAN-1F", "FAN-1R", "FAN-2F", "FAN-2R", "FAN-3F", "FAN-3R",
|
|
|
|
"FAN-4F", "FAN-4R", "FAN-5F", "FAN-5R", "FAN-6F", "FAN-6R", "FAN-7F", "FAN-7R"]
|
|
|
|
|
|
|
|
IPMI_OEM_NETFN = "0x3A"
|
|
|
|
IPMI_SENSOR_NETFN = "0x04"
|
|
|
|
IPMI_FAN_SPEED_CMD = "0x2D {}"
|
|
|
|
IPMI_AIR_FLOW_CMD = "0x0A {}"
|
|
|
|
IPMI_FAN_PRESENT_CMD = "0x06 0x03 {}"
|
|
|
|
IPMI_SET_FAN_LED_CMD = "0x07 {} {}"
|
|
|
|
IPMI_GET_FAN_LED_CMD = "0x08 {}"
|
|
|
|
IPMI_SET_PWM = "0x03 0x01 0x02 {} {}"
|
2019-12-04 21:00:13 -06:00
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
IPMI_FRU_MODEL_KEY = "Board Part Number"
|
|
|
|
IPMI_FRU_SERIAL_KEY = "Board Serial"
|
|
|
|
|
|
|
|
MAX_OUTLET = 24700
|
|
|
|
MAX_INLET = 29700
|
|
|
|
SPEED_TOLERANCE = 10
|
|
|
|
|
|
|
|
FAN1_FRONT_SS_ID = "0x0D"
|
|
|
|
FAN1_REAR_SS_ID = "0x45"
|
|
|
|
FAN_LED_OFF_CMD = "0x00"
|
|
|
|
FAN_LED_GREEN_CMD = "0x01"
|
|
|
|
FAN_LED_RED_CMD = "0x02"
|
|
|
|
FAN1_LED_CMD = "0x04"
|
|
|
|
FAN_PWM_REGISTER_START = 0x22
|
|
|
|
FAN_PWM_REGISTER_STEP = 0x10
|
|
|
|
FAN1_FRU_ID = 6
|
|
|
|
|
2019-12-04 21:00:13 -06:00
|
|
|
NUM_OF_FAN_TRAY = 7
|
|
|
|
PSU_FAN1_FRONT_SS_ID = "0x33"
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
|
|
|
|
class Fan(FanBase):
|
|
|
|
"""Platform-specific Fan class"""
|
|
|
|
|
|
|
|
def __init__(self, fan_tray_index, fan_index=0, is_psu_fan=False, psu_index=0):
|
2021-02-25 13:20:53 -06:00
|
|
|
FanBase.__init__(self)
|
2019-11-06 20:56:17 -06:00
|
|
|
self.fan_index = fan_index
|
|
|
|
self.fan_tray_index = fan_tray_index
|
|
|
|
self.is_psu_fan = is_psu_fan
|
|
|
|
if self.is_psu_fan:
|
|
|
|
self.psu_index = psu_index
|
|
|
|
self._api_helper = APIHelper()
|
|
|
|
self.index = self.fan_tray_index * 2 + self.fan_index
|
|
|
|
|
|
|
|
def get_direction(self):
|
|
|
|
"""
|
|
|
|
Retrieves the direction of fan
|
|
|
|
Returns:
|
|
|
|
A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST
|
|
|
|
depending on fan direction
|
|
|
|
"""
|
|
|
|
direction = self.FAN_DIRECTION_EXHAUST
|
2019-12-04 21:00:13 -06:00
|
|
|
fan_direction_key = hex(self.fan_tray_index) if not self.is_psu_fan else hex(
|
|
|
|
self.psu_index + NUM_OF_FAN_TRAY)
|
2019-11-06 20:56:17 -06:00
|
|
|
status, raw_flow = self._api_helper.ipmi_raw(
|
2019-12-04 21:00:13 -06:00
|
|
|
IPMI_OEM_NETFN, IPMI_AIR_FLOW_CMD.format(fan_direction_key))
|
2019-11-06 20:56:17 -06:00
|
|
|
if status and raw_flow == "01":
|
|
|
|
direction = self.FAN_DIRECTION_INTAKE
|
|
|
|
|
|
|
|
return direction
|
|
|
|
|
|
|
|
def get_speed(self):
|
|
|
|
"""
|
|
|
|
Retrieves the speed of fan as a percentage of full speed
|
|
|
|
Returns:
|
|
|
|
An integer, the percentage of full fan speed, in the range 0 (off)
|
|
|
|
to 100 (full speed)
|
|
|
|
|
|
|
|
Note:
|
|
|
|
M = 150
|
|
|
|
Max F2B = 24700 RPM
|
|
|
|
Max B2F = 29700 RPM
|
|
|
|
"""
|
|
|
|
|
|
|
|
max_rpm = MAX_OUTLET if self.fan_index % 2 == 0 else MAX_INLET
|
|
|
|
fan1_ss_start = FAN1_FRONT_SS_ID if self.fan_index % 2 == 0 else FAN1_REAR_SS_ID
|
|
|
|
|
2019-12-04 21:00:13 -06:00
|
|
|
ss_id = hex(int(fan1_ss_start, 16) + self.fan_tray_index) if not self.psu_index else hex(
|
|
|
|
int(PSU_FAN1_FRONT_SS_ID, 16) + self.fan_tray_index)
|
2019-11-06 20:56:17 -06:00
|
|
|
status, raw_ss_read = self._api_helper.ipmi_raw(
|
|
|
|
IPMI_SENSOR_NETFN, IPMI_FAN_SPEED_CMD.format(ss_id))
|
|
|
|
|
|
|
|
ss_read = raw_ss_read.split()[0]
|
|
|
|
rpm_speed = int(ss_read, 16)*150
|
|
|
|
speed = int(float(rpm_speed)/max_rpm * 100)
|
|
|
|
|
|
|
|
return speed
|
|
|
|
|
|
|
|
def get_target_speed(self):
|
|
|
|
"""
|
|
|
|
Retrieves the target (expected) speed of the fan
|
|
|
|
Returns:
|
|
|
|
An integer, the percentage of full fan speed, in the range 0 (off)
|
|
|
|
to 100 (full speed)
|
|
|
|
|
|
|
|
Note:
|
|
|
|
speed_pc = pwm_target/255*100
|
|
|
|
|
|
|
|
0 : when PWM mode is use
|
|
|
|
pwm : when pwm mode is not use
|
|
|
|
"""
|
|
|
|
target = 0
|
|
|
|
return target
|
|
|
|
|
|
|
|
def get_speed_tolerance(self):
|
|
|
|
"""
|
|
|
|
Retrieves the speed tolerance of the fan
|
|
|
|
Returns:
|
|
|
|
An integer, the percentage of variance from target speed which is
|
|
|
|
considered tolerable
|
|
|
|
"""
|
|
|
|
return SPEED_TOLERANCE
|
|
|
|
|
|
|
|
def set_speed(self, speed):
|
|
|
|
"""
|
|
|
|
Sets the fan speed
|
|
|
|
Args:
|
|
|
|
speed: An integer, the percentage of full fan speed to set fan to,
|
|
|
|
in the range 0 (off) to 100 (full speed)
|
|
|
|
Returns:
|
|
|
|
A boolean, True if speed is set successfully, False if not
|
|
|
|
Notes:
|
|
|
|
pwm setting mode must set as Manual
|
|
|
|
manual: ipmitool raw 0x3a 0x06 0x01 0x0
|
|
|
|
auto: ipmitool raw 0x3a 0x06 0x01 0x1
|
|
|
|
"""
|
|
|
|
# ipmitool raw 0x3a 0x03 0x01 0x02 {register} {pwm_speed}
|
|
|
|
# register = 22 32 42 52 62 72 82
|
|
|
|
|
2019-12-04 21:00:13 -06:00
|
|
|
if self.is_psu_fan:
|
2020-11-25 12:28:36 -06:00
|
|
|
# TODO
|
2019-12-04 21:00:13 -06:00
|
|
|
return False
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
speed_hex = hex(int(float(speed)/100 * 255))
|
|
|
|
fan_register_hex = hex(FAN_PWM_REGISTER_START +
|
|
|
|
(self.fan_tray_index*FAN_PWM_REGISTER_STEP))
|
|
|
|
|
|
|
|
set_speed_cmd = IPMI_SET_PWM.format(fan_register_hex, speed_hex)
|
|
|
|
status, set_speed_res = self._api_helper.ipmi_raw(
|
|
|
|
IPMI_OEM_NETFN, set_speed_cmd)
|
|
|
|
|
|
|
|
set_speed = False if not status else True
|
|
|
|
|
|
|
|
return set_speed
|
|
|
|
|
|
|
|
def set_status_led(self, color):
|
|
|
|
"""
|
|
|
|
Sets the state of the fan module status LED
|
|
|
|
Args:
|
|
|
|
color: A string representing the color with which to set the
|
|
|
|
fan module status LED
|
|
|
|
Returns:
|
|
|
|
bool: True if status LED state is set successfully, False if not
|
|
|
|
|
|
|
|
Note:
|
|
|
|
LED setting mode must set as Manual
|
|
|
|
manual: ipmitool raw 0x3A 0x09 0x02 0x00
|
|
|
|
auto: ipmitool raw 0x3A 0x09 0x02 0x01
|
|
|
|
"""
|
2019-12-04 21:00:13 -06:00
|
|
|
|
|
|
|
if self.is_psu_fan:
|
|
|
|
# Not support
|
|
|
|
return False
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
led_cmd = {
|
|
|
|
self.STATUS_LED_COLOR_GREEN: FAN_LED_GREEN_CMD,
|
|
|
|
self.STATUS_LED_COLOR_RED: FAN_LED_RED_CMD,
|
|
|
|
self.STATUS_LED_COLOR_OFF: FAN_LED_OFF_CMD
|
|
|
|
}.get(color)
|
|
|
|
|
|
|
|
fan_selector = hex(int(FAN1_LED_CMD, 16) + self.fan_tray_index)
|
|
|
|
status, set_led = self._api_helper.ipmi_raw(
|
|
|
|
IPMI_OEM_NETFN, IPMI_SET_FAN_LED_CMD.format(fan_selector, led_cmd))
|
|
|
|
set_status_led = False if not status else True
|
|
|
|
|
|
|
|
return set_status_led
|
|
|
|
|
|
|
|
def get_status_led(self):
|
|
|
|
"""
|
|
|
|
Gets the state of the fan status LED
|
|
|
|
Returns:
|
|
|
|
A string, one of the predefined STATUS_LED_COLOR_* strings above
|
|
|
|
|
|
|
|
Note:
|
|
|
|
STATUS_LED_COLOR_GREEN = "green"
|
|
|
|
STATUS_LED_COLOR_AMBER = "amber"
|
|
|
|
STATUS_LED_COLOR_RED = "red"
|
|
|
|
STATUS_LED_COLOR_OFF = "off"
|
|
|
|
"""
|
2019-12-04 21:00:13 -06:00
|
|
|
if self.is_psu_fan:
|
|
|
|
# Not support
|
|
|
|
return self.STATUS_LED_COLOR_OFF
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
fan_selector = hex(int(FAN1_LED_CMD, 16) + self.fan_tray_index)
|
|
|
|
status, hx_color = self._api_helper.ipmi_raw(
|
|
|
|
IPMI_OEM_NETFN, IPMI_GET_FAN_LED_CMD.format(fan_selector))
|
|
|
|
|
|
|
|
status_led = {
|
|
|
|
"00": self.STATUS_LED_COLOR_OFF,
|
|
|
|
"01": self.STATUS_LED_COLOR_GREEN,
|
|
|
|
"02": self.STATUS_LED_COLOR_RED,
|
|
|
|
}.get(hx_color, self.STATUS_LED_COLOR_OFF)
|
|
|
|
|
|
|
|
return status_led
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
"""
|
|
|
|
Retrieves the name of the device
|
|
|
|
Returns:
|
|
|
|
string: The name of the device
|
|
|
|
"""
|
|
|
|
fan_name = FAN_NAME_LIST[self.fan_tray_index*2 + self.fan_index] if not self.is_psu_fan else "PSU-{} FAN-{}".format(
|
|
|
|
self.psu_index+1, self.fan_index+1)
|
|
|
|
|
|
|
|
return fan_name
|
|
|
|
|
|
|
|
def get_presence(self):
|
|
|
|
"""
|
|
|
|
Retrieves the presence of the FAN
|
|
|
|
Returns:
|
|
|
|
bool: True if FAN is present, False if not
|
|
|
|
"""
|
2019-12-04 21:00:13 -06:00
|
|
|
if self.is_psu_fan:
|
|
|
|
return True
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
presence = False
|
|
|
|
status, raw_present = self._api_helper.ipmi_raw(
|
|
|
|
IPMI_OEM_NETFN, IPMI_FAN_PRESENT_CMD.format(hex(self.index)))
|
|
|
|
if status and raw_present == "00":
|
|
|
|
presence = True
|
|
|
|
|
|
|
|
return presence
|
|
|
|
|
|
|
|
def get_model(self):
|
|
|
|
"""
|
|
|
|
Retrieves the model number (or part number) of the device
|
|
|
|
Returns:
|
|
|
|
string: Model/part number of device
|
|
|
|
"""
|
2019-12-04 21:00:13 -06:00
|
|
|
if self.is_psu_fan:
|
|
|
|
return "Unknown"
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
model = "Unknown"
|
|
|
|
ipmi_fru_idx = self.fan_tray_index + FAN1_FRU_ID
|
|
|
|
status, raw_model = self._api_helper.ipmi_fru_id(
|
|
|
|
ipmi_fru_idx, IPMI_FRU_MODEL_KEY)
|
|
|
|
|
|
|
|
fru_pn_list = raw_model.split()
|
|
|
|
if len(fru_pn_list) > 4:
|
|
|
|
model = fru_pn_list[4]
|
|
|
|
|
|
|
|
return model
|
|
|
|
|
|
|
|
def get_serial(self):
|
|
|
|
"""
|
|
|
|
Retrieves the serial number of the device
|
|
|
|
Returns:
|
|
|
|
string: Serial number of device
|
|
|
|
"""
|
2019-12-04 21:00:13 -06:00
|
|
|
if self.is_psu_fan:
|
|
|
|
return "Unknown"
|
|
|
|
|
2019-11-06 20:56:17 -06:00
|
|
|
serial = "Unknown"
|
|
|
|
ipmi_fru_idx = self.fan_tray_index + FAN1_FRU_ID
|
|
|
|
status, raw_model = self._api_helper.ipmi_fru_id(
|
|
|
|
ipmi_fru_idx, IPMI_FRU_SERIAL_KEY)
|
|
|
|
|
|
|
|
fru_sr_list = raw_model.split()
|
|
|
|
if len(fru_sr_list) > 3:
|
|
|
|
serial = fru_sr_list[3]
|
|
|
|
|
|
|
|
return serial
|
|
|
|
|
|
|
|
def get_status(self):
|
|
|
|
"""
|
|
|
|
Retrieves the operational status of the device
|
|
|
|
Returns:
|
|
|
|
A boolean value, True if device is operating properly, False if not
|
|
|
|
"""
|
|
|
|
return self.get_presence() and self.get_speed() > 0
|