diff --git a/platform/broadcom/sonic-platform-modules-dell/s6000/modules/dell_s6000_platform.c b/platform/broadcom/sonic-platform-modules-dell/s6000/modules/dell_s6000_platform.c index 0463ac30e589..e8bcd312168f 100644 --- a/platform/broadcom/sonic-platform-modules-dell/s6000/modules/dell_s6000_platform.c +++ b/platform/broadcom/sonic-platform-modules-dell/s6000/modules/dell_s6000_platform.c @@ -549,6 +549,18 @@ static ssize_t get_psu1_status(struct device *dev, struct device_attribute *deva return sprintf(buf, "%d\n", data); } +static ssize_t get_powersupply_status(struct device *dev, struct device_attribute *devattr, char *buf) +{ + int data; + struct cpld_platform_data *pdata = dev->platform_data; + + data = dell_i2c_smbus_read_byte_data(pdata[master_cpld].client, 0x3); + if (data < 0) + return sprintf(buf, "read error"); + + return sprintf(buf, "%x\n", data); +} + static ssize_t get_system_led(struct device *dev, struct device_attribute *devattr, char *buf) { int ret; @@ -1126,6 +1138,7 @@ static DEVICE_ATTR(psu0_prs, S_IRUGO, get_psu0_prs, NULL); static DEVICE_ATTR(psu1_prs, S_IRUGO, get_psu1_prs, NULL); static DEVICE_ATTR(psu0_status, S_IRUGO, get_psu0_status, NULL); static DEVICE_ATTR(psu1_status, S_IRUGO, get_psu1_status, NULL); +static DEVICE_ATTR(powersupply_status, S_IRUGO, get_powersupply_status, NULL); static DEVICE_ATTR(system_led, S_IRUGO | S_IWUSR, get_system_led, set_system_led); static DEVICE_ATTR(locator_led, S_IRUGO | S_IWUSR, get_locator_led, set_locator_led); static DEVICE_ATTR(power_led, S_IRUGO | S_IWUSR, get_power_led, set_power_led); @@ -1150,6 +1163,7 @@ static struct attribute *s6000_cpld_attrs[] = { &dev_attr_psu1_prs.attr, &dev_attr_psu0_status.attr, &dev_attr_psu1_status.attr, + &dev_attr_powersupply_status.attr, &dev_attr_system_led.attr, &dev_attr_locator_led.attr, &dev_attr_power_led.attr, diff --git a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/__init__.py b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/__init__.py index 1e26181263a5..48057d290357 100755 --- a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/__init__.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/__init__.py @@ -1,2 +1,2 @@ -__all__ = ["platform", "chassis", "sfp"] +__all__ = ["platform", "chassis", "sfp", "psu", "thermal"] from sonic_platform import * diff --git a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/chassis.py b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/chassis.py index ba85c1f5894c..452598773651 100755 --- a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/chassis.py +++ b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/chassis.py @@ -16,11 +16,15 @@ from sonic_platform.sfp import Sfp from sonic_platform.eeprom import Eeprom from sonic_platform.fan import Fan + from sonic_platform.psu import Psu + from sonic_platform.thermal import Thermal except ImportError as e: raise ImportError(str(e) + "- required module not found") MAX_S6000_FAN = 3 +MAX_S6000_PSU = 2 +MAX_S6000_THERMAL = 10 BIOS_QUERY_VERSION_COMMAND = "dmidecode -s system-version" #components definitions @@ -75,6 +79,14 @@ def __init__(self): fan = Fan(i) self._fan_list.append(fan) + for i in range(MAX_S6000_PSU): + psu = Psu(i) + self._psu_list.append(psu) + + for i in range(MAX_S6000_THERMAL): + thermal = Thermal(i) + self._thermal_list.append(thermal) + # Initialize component list self._component_name_list.append(COMPONENT_BIOS) self._component_name_list.append(COMPONENT_CPLD1) diff --git a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/psu.py b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/psu.py new file mode 100644 index 000000000000..dfbd2a87eb5d --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/psu.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python + +######################################################################## +# DellEMC S6000 +# +# 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.eeprom import Eeprom +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +class Psu(PsuBase): + """DellEMC Platform-specific PSU class""" + + CPLD_DIR = "/sys/devices/platform/dell-s6000-cpld.0/" + I2C_DIR = "/sys/class/i2c-adapter/" + + def __init__(self, psu_index): + # PSU is 1-based in DellEMC platforms + self.index = psu_index + 1 + self.psu_presence_reg = "psu{}_prs".format(psu_index) + self.psu_status_reg = "powersupply_status" + + if self.index == 1: + ltc_dir = self.I2C_DIR + "i2c-11/11-0042/hwmon/" + else: + ltc_dir = self.I2C_DIR + "i2c-11/11-0040/hwmon/" + hwmon_node = os.listdir(ltc_dir)[0] + self.HWMON_DIR = ltc_dir + hwmon_node + '/' + + self.psu_voltage_reg = self.HWMON_DIR + "in1_input" + self.psu_current_reg = self.HWMON_DIR + "curr1_input" + self.psu_power_reg = self.HWMON_DIR + "power1_input" + + self.eeprom = Eeprom(is_psu=True, psu_index=self.index) + + # Overriding _fan_list class variable defined in PsuBase, to + # make it unique per Psu object + self._fan_list = [] + + def _get_cpld_register(self, reg_name): + # On successful read, returns the value read from given + # reg_name and on failure returns 'ERR' + rv = 'ERR' + cpld_reg_file = self.CPLD_DIR + reg_name + + if (not os.path.isfile(cpld_reg_file)): + return rv + + try: + with open(cpld_reg_file, 'r') as fd: + rv = fd.read() + except: + rv = 'ERR' + + rv = rv.rstrip('\r\n') + rv = rv.lstrip(" ") + return rv + + def _get_i2c_register(self, reg_file): + # On successful read, returns the value read from given + # reg_name and on failure returns 'ERR' + rv = 'ERR' + + if (not os.path.isfile(reg_file)): + return rv + + try: + with open(reg_file, 'r') as fd: + rv = fd.read() + except: + 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_cpld_register(self.psu_presence_reg) + if (psu_presence != 'ERR'): + psu_presence = int(psu_presence) + if psu_presence: + status = True + + return status + + def get_model(self): + """ + Retrieves the part number of the PSU + + Returns: + string: Part number of PSU + """ + return self.eeprom.part_number_str() + + 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" + return self.eeprom.serial_number_str() + + 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_cpld_register(self.psu_status_reg) + if (psu_status != 'ERR'): + psu_status = (int(psu_status, 16) >> ((2 - self.index) * 4)) & 0xF + 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_i2c_register(self.psu_voltage_reg) + if (psu_voltage != 'ERR') and self.get_status(): + # 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_i2c_register(self.psu_current_reg) + if (psu_current != 'ERR') and self.get_status(): + # 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_i2c_register(self.psu_power_reg) + if (psu_power != 'ERR') and self.get_status(): + # 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 + psu_status = self._get_cpld_register(self.psu_status_reg) + if (psu_status != 'ERR'): + psu_status = (int(psu_status, 16) >> ((2 - self.index) * 4)) & 0xF + if (psu_status == 0x2): + 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 S6000, the firmware running in the PSU controls the LED + # and the PSU LED state cannot be changed from CPU. + return False diff --git a/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/thermal.py b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/thermal.py new file mode 100644 index 000000000000..70bac0c729d2 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-dell/s6000/sonic_platform/thermal.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python + +######################################################################## +# DellEMC S6000 +# +# 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 + from sonic_platform.psu import Psu +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +class Thermal(ThermalBase): + """DellEMC Platform-specific Thermal class""" + + I2C_DIR = "/sys/class/i2c-adapter/" + I2C_DEV_MAPPING = (['i2c-11/11-004c/hwmon/', 1], + ['i2c-11/11-004d/hwmon/', 1], + ['i2c-11/11-004e/hwmon/', 1], + ['i2c-10/10-0018/hwmon/', 1], + ['i2c-1/1-0059/hwmon/', 1], + ['i2c-1/1-0059/hwmon/', 2], + ['i2c-1/1-0058/hwmon/', 1], + ['i2c-1/1-0058/hwmon/', 2]) + THERMAL_NAME = ('ASIC On-board', 'NIC', 'System Front', 'DIMM', + 'PSU1-Sensor 1', 'PSU1-Sensor 2', 'PSU2-Sensor 1', + 'PSU2-Sensor 2', 'CPU Core 0', 'CPU Core 1') + + def __init__(self, thermal_index): + self.index = thermal_index + 1 + self.is_psu_thermal = False + self.dependency = None + + if self.index < 9: + i2c_path = self.I2C_DIR + self.I2C_DEV_MAPPING[self.index - 1][0] + hwmon_temp_index = self.I2C_DEV_MAPPING[self.index - 1][1] + hwmon_temp_suffix = "max" + hwmon_node = os.listdir(i2c_path)[0] + self.HWMON_DIR = i2c_path + hwmon_node + '/' + + if self.index == 4: + hwmon_temp_suffix = "crit" + + if self.index > 4: + self.is_psu_thermal = True + self.dependency = Psu(self.index / 7) + else: + dev_path = "/sys/devices/platform/coretemp.0/hwmon/" + hwmon_temp_index = self.index - 7 + hwmon_temp_suffix = "crit" + hwmon_node = os.listdir(dev_path)[0] + self.HWMON_DIR = dev_path + hwmon_node + '/' + + self.thermal_temperature_file = self.HWMON_DIR \ + + "temp{}_input".format(hwmon_temp_index) + self.thermal_high_threshold_file = self.HWMON_DIR \ + + "temp{}_{}".format(hwmon_temp_index, hwmon_temp_suffix) + 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: + 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 + """ + if self.dependency: + return self.dependency.get_presence() + else: + 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 + """ + if self.dependency: + return self.dependency.get_status() + else: + return True + + 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