DellEMC Z9100 : Platform2.0 API implementation [PSU, Thermal] (#3361)

* DellEMC Z9100 : Platform2.0 API implementation [PSU, Thermal]

* DellEMC Z9100 : Platform2.0 API [ Thermal ]

* DellEMC Z9100 : Platform2.0 API [PSU, Thermal]
This commit is contained in:
Arun Saravanan Balachandran 2019-09-19 03:39:43 +05:30 committed by Sujin Kang
parent 6ca0d7b197
commit 5c9348b093
6 changed files with 505 additions and 3 deletions

View File

@ -1,3 +1,3 @@
__all__ = ["platform", "chassis", "sfp"] __all__ = ["platform", "chassis", "fan", "psu", "sfp", "thermal"]
from sonic_platform import * from sonic_platform import *

View File

@ -15,6 +15,8 @@ try:
from sonic_platform_base.chassis_base import ChassisBase from sonic_platform_base.chassis_base import ChassisBase
from sonic_platform.sfp import Sfp from sonic_platform.sfp import Sfp
from sonic_platform.fan import Fan from sonic_platform.fan import Fan
from sonic_platform.psu import Psu
from sonic_platform.thermal import Thermal
from eeprom import Eeprom from eeprom import Eeprom
except ImportError as e: except ImportError as e:
raise ImportError(str(e) + "- required module not found") raise ImportError(str(e) + "- required module not found")
@ -23,6 +25,7 @@ except ImportError as e:
MAX_Z9100_FANTRAY = 5 MAX_Z9100_FANTRAY = 5
MAX_Z9100_FAN = 2 MAX_Z9100_FAN = 2
MAX_Z9100_PSU = 2 MAX_Z9100_PSU = 2
MAX_Z9100_THERMAL = 8
BIOS_QUERY_VERSION_COMMAND = "dmidecode -s system-version" BIOS_QUERY_VERSION_COMMAND = "dmidecode -s system-version"
#components definitions #components definitions
@ -104,6 +107,14 @@ class Chassis(ChassisBase):
fan = Fan(i, j) fan = Fan(i, j)
self._fan_list.append(fan) self._fan_list.append(fan)
for i in range(MAX_Z9100_PSU):
psu = Psu(i)
self._psu_list.append(psu)
for i in range(MAX_Z9100_THERMAL):
thermal = Thermal(i)
self._thermal_list.append(thermal)
# Initialize component list # Initialize component list
self._component_name_list.append(COMPONENT_BIOS) self._component_name_list.append(COMPONENT_BIOS)
self._component_name_list.append(SWITCH_CPLD1) self._component_name_list.append(SWITCH_CPLD1)
@ -191,6 +202,17 @@ class Chassis(ChassisBase):
""" """
return self.sys_eeprom.serial_number_str() return self.sys_eeprom.serial_number_str()
def get_system_eeprom_info(self):
"""
Retrieves the full content of system EEPROM information for the chassis
Returns:
A dictionary where keys are the type code defined in
OCP ONIE TlvInfo EEPROM format and values are their corresponding
values.
"""
return self.sys_eeprom.system_eeprom_info()
def get_reboot_cause(self): def get_reboot_cause(self):
""" """
Retrieves the cause of the previous reboot Retrieves the cause of the previous reboot

View File

@ -21,11 +21,42 @@ class Eeprom(eeprom_tlvinfo.TlvInfoDecoder):
def __init__(self): def __init__(self):
self.eeprom_path = "/sys/class/i2c-adapter/i2c-2/2-0050/eeprom" self.eeprom_path = "/sys/class/i2c-adapter/i2c-2/2-0050/eeprom"
super(Eeprom, self).__init__(self.eeprom_path, 0, '', True) super(Eeprom, self).__init__(self.eeprom_path, 0, '', True)
self.eeprom_tlv_dict = dict()
try: try:
self.eeprom_data = self.read_eeprom() self.eeprom_data = self.read_eeprom()
except: except:
self.eeprom_data = "N/A" self.eeprom_data = "N/A"
raise RuntimeError("Eeprom is not Programmed") raise RuntimeError("Eeprom is not Programmed")
else:
eeprom = self.eeprom_data
if not self.is_valid_tlvinfo_header(eeprom):
return
total_length = (ord(eeprom[9]) << 8) | ord(eeprom[10])
tlv_index = self._TLV_INFO_HDR_LEN
tlv_end = self._TLV_INFO_HDR_LEN + total_length
while (tlv_index + 2) < len(eeprom) and tlv_index < tlv_end:
if not self.is_valid_tlv(eeprom[tlv_index:]):
break
tlv = eeprom[tlv_index:tlv_index + 2
+ ord(eeprom[tlv_index + 1])]
code = "0x%02X" % (ord(tlv[0]))
if ord(tlv[0]) == self._TLV_CODE_VENDOR_EXT:
value = str((ord(tlv[2]) << 24) | (ord(tlv[3]) << 16) |
(ord(tlv[4]) << 8) | ord(tlv[5]))
value += str(tlv[6:6 + ord(tlv[1])])
else:
name, value = self.decoder(None, tlv)
self.eeprom_tlv_dict[code] = value
if ord(eeprom[tlv_index]) == self._TLV_CODE_CRC_32:
break
tlv_index += ord(eeprom[tlv_index+1]) + 2
def serial_number_str(self): def serial_number_str(self):
(is_valid, results) = self.get_tlv_field( (is_valid, results) = self.get_tlv_field(
@ -74,3 +105,10 @@ class Eeprom(eeprom_tlvinfo.TlvInfoDecoder):
return results[2] return results[2]
def system_eeprom_info(self):
"""
Returns a dictionary, where keys are the type code defined in
ONIE EEPROM format and values are their corresponding values
found in the system EEPROM.
"""
return self.eeprom_tlv_dict

View File

@ -26,7 +26,7 @@ class Fan(FanBase):
HWMON_NODE = os.listdir(HWMON_DIR)[0] HWMON_NODE = os.listdir(HWMON_DIR)[0]
MAILBOX_DIR = HWMON_DIR + HWMON_NODE MAILBOX_DIR = HWMON_DIR + HWMON_NODE
def __init__(self, fantray_index, fan_index=1, psu_fan=False): def __init__(self, fantray_index=1, fan_index=1, psu_fan=False):
self.is_psu_fan = psu_fan self.is_psu_fan = psu_fan
if not self.is_psu_fan: if not self.is_psu_fan:
# API index is starting from 0, DellEMC platform index is starting # API index is starting from 0, DellEMC platform index is starting
@ -73,7 +73,7 @@ class Fan(FanBase):
if not self.is_psu_fan: if not self.is_psu_fan:
return "FanTray{}-Fan{}".format(self.fantrayindex, self.fanindex) return "FanTray{}-Fan{}".format(self.fantrayindex, self.fanindex)
else: else:
return "PSU{} Fan".format(self.index - 10) return "PSU{} Fan".format(self.fanindex - 10)
def get_model(self): def get_model(self):
""" """

View File

@ -0,0 +1,237 @@
#!/usr/bin/env python
########################################################################
# DellEMC Z9100
#
# Module contains an implementation of SONiC Platform Base API and
# provides the PSUs' information which are available in the platform
#
########################################################################
try:
import os
from sonic_platform_base.psu_base import PsuBase
from sonic_platform.fan import Fan
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
class Psu(PsuBase):
"""DellEMC Platform-specific PSU class"""
HWMON_DIR = "/sys/devices/platform/SMF.512/hwmon/"
HWMON_NODE = os.listdir(HWMON_DIR)[0]
MAILBOX_DIR = HWMON_DIR + HWMON_NODE
def __init__(self, psu_index):
# PSU is 1-based in DellEMC platforms
self.index = psu_index + 1
self.psu_presence_reg = "psu{}_presence".format(self.index)
self.psu_serialno_reg = "psu{}_serialno".format(self.index)
if self.index == 1:
self.psu_voltage_reg = "in30_input"
self.psu_current_reg = "curr602_input"
self.psu_power_reg = "power2_input"
elif self.index == 2:
self.psu_voltage_reg = "in32_input"
self.psu_current_reg = "curr702_input"
self.psu_power_reg = "power4_input"
# Overriding _fan_list class variable defined in PsuBase, to
# make it unique per Psu object
self._fan_list = []
# Passing True to specify it is a PSU fan
psu_fan = Fan(fan_index=self.index, psu_fan=True)
self._fan_list.append(psu_fan)
def _get_pmc_register(self, reg_name):
# On successful read, returns the value read from given
# reg_name and on failure returns 'ERR'
rv = 'ERR'
mb_reg_file = self.MAILBOX_DIR + '/' + reg_name
if (not os.path.isfile(mb_reg_file)):
return rv
try:
with open(mb_reg_file, 'r') as fd:
rv = fd.read()
except Exception as error:
rv = 'ERR'
rv = rv.rstrip('\r\n')
rv = rv.lstrip(" ")
return rv
def get_name(self):
"""
Retrieves the name of the device
Returns:
string: The name of the device
"""
return "PSU{}".format(self.index)
def get_presence(self):
"""
Retrieves the presence of the Power Supply Unit (PSU)
Returns:
bool: True if PSU is present, False if not
"""
status = False
psu_presence = self._get_pmc_register(self.psu_presence_reg)
if (psu_presence != 'ERR'):
psu_presence = int(psu_presence, 16)
# Checking whether bit 0 is not set
if (~psu_presence & 0b1):
status = True
return status
def get_model(self):
"""
Retrieves the part number of the PSU
Returns:
string: Part number of PSU
"""
# For Serial number "US-01234D-54321-25A-0123-A00", the part
# number is "01234D"
psu_serialno = self._get_pmc_register(self.psu_serialno_reg)
if (psu_serialno != 'ERR') and self.get_presence():
if (len(psu_serialno.split('-')) > 1):
psu_partno = psu_serialno.split('-')[1]
else:
psu_partno = 'NA'
else:
psu_partno = 'NA'
return psu_partno
def get_serial(self):
"""
Retrieves the serial number of the PSU
Returns:
string: Serial number of PSU
"""
# Sample Serial number format "US-01234D-54321-25A-0123-A00"
psu_serialno = self._get_pmc_register(self.psu_serialno_reg)
if (psu_serialno == 'ERR') or not self.get_presence():
psu_serialno = 'NA'
return psu_serialno
def get_status(self):
"""
Retrieves the operational status of the PSU
Returns:
bool: True if PSU is operating properly, False if not
"""
status = False
psu_status = self._get_pmc_register(self.psu_presence_reg)
if (psu_status != 'ERR'):
psu_status = int(psu_status, 16)
# Checking whether both bit 3 and bit 2 are not set
if (~psu_status & 0b1000) and (~psu_status & 0b0100):
status = True
return status
def get_voltage(self):
"""
Retrieves current PSU voltage output
Returns:
A float number, the output voltage in volts,
e.g. 12.1
"""
psu_voltage = self._get_pmc_register(self.psu_voltage_reg)
if (psu_voltage != 'ERR') and self.get_presence():
# Converting the value returned by driver which is in
# millivolts to volts
psu_voltage = float(psu_voltage) / 1000
else:
psu_voltage = 0.0
return psu_voltage
def get_current(self):
"""
Retrieves present electric current supplied by PSU
Returns:
A float number, electric current in amperes,
e.g. 15.4
"""
psu_current = self._get_pmc_register(self.psu_current_reg)
if (psu_current != 'ERR') and self.get_presence():
# Converting the value returned by driver which is in
# milliamperes to amperes
psu_current = float(psu_current) / 1000
else:
psu_current = 0.0
return psu_current
def get_power(self):
"""
Retrieves current energy supplied by PSU
Returns:
A float number, the power in watts,
e.g. 302.6
"""
psu_power = self._get_pmc_register(self.psu_power_reg)
if (psu_power != 'ERR') and self.get_presence():
# Converting the value returned by driver which is in
# microwatts to watts
psu_power = float(psu_power) / 1000000
else:
psu_power = 0.0
return psu_power
def get_powergood_status(self):
"""
Retrieves the powergood status of PSU
Returns:
A boolean, True if PSU has stablized its output voltages and
passed all its internal self-tests, False if not.
"""
status = False
if self.get_status() and self._fan_list[0].get_status():
status = True
return status
def get_status_led(self):
"""
Gets the state of the PSU status LED
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings.
"""
if self.get_powergood_status():
return self.STATUS_LED_COLOR_GREEN
else:
return self.STATUS_LED_COLOR_OFF
def set_status_led(self, color):
"""
Sets the state of the PSU status LED
Args:
color: A string representing the color with which to set the
PSU status LED
Returns:
bool: True if status LED state is set successfully, False if
not
"""
# In Z9100, SmartFusion FPGA controls the PSU LED and the PSU
# LED state cannot be changed from CPU.
return False

View File

@ -0,0 +1,205 @@
#!/usr/bin/env python
########################################################################
# DellEMC Z9100
#
# Module contains an implementation of SONiC Platform Base API and
# provides the Thermals' information which are available in the platform
#
########################################################################
try:
import os
from sonic_platform_base.thermal_base import ThermalBase
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
class Thermal(ThermalBase):
"""DellEMC Platform-specific Thermal class"""
THERMAL_NAME = (
'CPU On-board', 'ASIC On-board Rear', 'System Front Left',
'System Front Right', 'CPU Core 0', 'CPU Core 1', 'CPU Core 2',
'CPU Core 3'
)
def __init__(self, thermal_index):
self.is_cpu_thermal = False
self.index = thermal_index + 1
if self.index < 5:
hwmon_temp_index = self.index
dev_path = "/sys/devices/platform/SMF.512/hwmon/"
else:
hwmon_temp_index = self.index - 3
self.is_cpu_thermal = True
dev_path = "/sys/devices/platform/coretemp.0/hwmon/"
hwmon_node = os.listdir(dev_path)[0]
self.HWMON_DIR = dev_path + hwmon_node + '/'
self.thermal_status_file = self.HWMON_DIR \
+ "temp{}_alarm".format(hwmon_temp_index)
self.thermal_temperature_file = self.HWMON_DIR \
+ "temp{}_input".format(hwmon_temp_index)
self.thermal_high_threshold_file = self.HWMON_DIR \
+ "temp{}_crit".format(hwmon_temp_index)
self.thermal_low_threshold_file = self.HWMON_DIR \
+ "temp{}_min".format(hwmon_temp_index)
def _read_sysfs_file(self, sysfs_file):
# On successful read, returns the value read from given
# sysfs_file and on failure returns 'ERR'
rv = 'ERR'
if (not os.path.isfile(sysfs_file)):
return rv
try:
with open(sysfs_file, 'r') as fd:
rv = fd.read()
except Exception as error:
rv = 'ERR'
rv = rv.rstrip('\r\n')
rv = rv.lstrip(" ")
return rv
def get_name(self):
"""
Retrieves the name of the thermal
Returns:
string: The name of the thermal
"""
return self.THERMAL_NAME[self.index - 1]
def get_presence(self):
"""
Retrieves the presence of the thermal
Returns:
bool: True if thermal is present, False if not
"""
return True
def get_model(self):
"""
Retrieves the model number (or part number) of the Thermal
Returns:
string: Model/part number of Thermal
"""
return 'NA'
def get_serial(self):
"""
Retrieves the serial number of the Thermal
Returns:
string: Serial number of Thermal
"""
return 'NA'
def get_status(self):
"""
Retrieves the operational status of the thermal
Returns:
A boolean value, True if thermal is operating properly,
False if not
"""
status = False
if self.is_cpu_thermal:
status = True
else:
thermal_status = self._read_sysfs_file(self.thermal_status_file)
if (thermal_status != 'ERR'):
thermal_status = int(thermal_status, 16)
if thermal_status != 5:
status = True
return status
def get_temperature(self):
"""
Retrieves current temperature reading from thermal
Returns:
A float number of current temperature in Celsius up to
nearest thousandth of one degree Celsius, e.g. 30.125
"""
thermal_temperature = self._read_sysfs_file(
self.thermal_temperature_file)
if (thermal_temperature != 'ERR'):
thermal_temperature = float(thermal_temperature) / 1000
else:
thermal_temperature = 0
return "{:.3f}".format(thermal_temperature)
def get_high_threshold(self):
"""
Retrieves the high threshold temperature of thermal
Returns:
A float number, the high threshold temperature of thermal in
Celsius up to nearest thousandth of one degree Celsius,
e.g. 30.125
"""
thermal_high_threshold = self._read_sysfs_file(
self.thermal_high_threshold_file)
if (thermal_high_threshold != 'ERR'):
thermal_high_threshold = float(thermal_high_threshold) / 1000
else:
thermal_high_threshold = 0
return "{:.3f}".format(thermal_high_threshold)
def get_low_threshold(self):
"""
Retrieves the low threshold temperature of thermal
Returns:
A float number, the low threshold temperature of thermal in
Celsius up to nearest thousandth of one degree Celsius,
e.g. 30.125
"""
thermal_low_threshold = self._read_sysfs_file(
self.thermal_low_threshold_file)
if (thermal_low_threshold != 'ERR'):
thermal_low_threshold = float(thermal_low_threshold) / 1000
else:
thermal_low_threshold = 0
return "{:.3f}".format(thermal_low_threshold)
def set_high_threshold(self, temperature):
"""
Sets the high threshold temperature of thermal
Args :
temperature: A float number up to nearest thousandth of one
degree Celsius, e.g. 30.125
Returns:
A boolean, True if threshold is set successfully, False if
not
"""
# Thermal threshold values are pre-defined based on HW.
return False
def set_low_threshold(self, temperature):
"""
Sets the low threshold temperature of thermal
Args :
temperature: A float number up to nearest thousandth of one
degree Celsius, e.g. 30.125
Returns:
A boolean, True if threshold is set successfully, False if
not
"""
# Thermal threshold values are pre-defined based on HW.
return False