From 191f97897d2818676692b1708871b9bfceb34952 Mon Sep 17 00:00:00 2001 From: Wirut Getbamrung Date: Mon, 13 Apr 2020 17:18:24 +0700 Subject: [PATCH] [device/celestica]: Implement Questone2BD platform API version 1.0 (#149) * add questone2bd sensor config * add questone2bd sonic_platform api * add questone2bd sonic_platform api installer * update questone2bd api to skip exception * fix psu api/sensor * fix psu i2c on fan api --- .../x86_64-cel_questone2bd-r0/sensors.conf | 86 ++ .../sonic_platform/__init__.py | 2 + .../sonic_platform/chassis.py | 213 +++ .../sonic_platform/component.py | 143 ++ .../sonic_platform/eeprom.py | 118 ++ .../sonic_platform/fan.py | 295 ++++ .../sonic_platform/helper.py | 200 +++ .../sonic_platform/platform.py | 23 + .../sonic_platform/psu.py | 234 +++ .../sonic_platform/sfp.py | 1311 +++++++++++++++++ .../sonic_platform/thermal.py | 237 +++ .../sonic_platform/watchdog.py | 233 +++ .../platform-modules-questone2bd.install | 4 +- .../platform-modules-questone2bd.postinst | 2 + .../questone2bd/setup.py | 34 + 15 files changed, 3134 insertions(+), 1 deletion(-) create mode 100755 device/celestica/x86_64-cel_questone2bd-r0/sensors.conf create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/__init__.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/chassis.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/component.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/eeprom.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/fan.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/helper.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/platform.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/psu.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/sfp.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/thermal.py create mode 100644 device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/watchdog.py create mode 100644 platform/broadcom/sonic-platform-modules-cel/questone2bd/setup.py diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sensors.conf b/device/celestica/x86_64-cel_questone2bd-r0/sensors.conf new file mode 100755 index 000000000000..c168e3c8e141 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sensors.conf @@ -0,0 +1,86 @@ +chip "syscpld-i2c-*-0d" + label temp1 "Switch temp" + set temp1_max_hyst 98 + set temp1_max 104 + ignore temp2 + +chip "fancpld-i2c-*-0d" + label fan1 "Fan 4 Front" + label fan2 "Fan 4 Rear" + label fan3 "Fan 3 Front" + label fan4 "Fan 3 Rear" + label fan5 "Fan 2 Front" + label fan6 "Fan 2 Rear" + label fan7 "Fan 1 Front" + label fan8 "Fan 1 Rear" + set fan1_min 1000 + set fan1_max 27170 + set fan2_min 1000 + set fan2_max 32670 + set fan3_min 1000 + set fan3_max 27170 + set fan4_min 1000 + set fan4_max 32670 + set fan5_min 1000 + set fan5_max 27170 + set fan6_min 1000 + set fan6_max 32670 + set fan7_min 1000 + set fan7_max 27170 + set fan8_min 1000 + set fan8_max 32670 + ignore fan9 + ignore fan10 + +chip "lm75b-i2c-*-4d" + compute temp1 @+(0), @/(0) + +chip "lm75b-i2c-*-48" + set temp1_max 70 + set temp1_max_hyst 60 + +chip "dps1100-i2c-*-58" + set in1_min 90 + set in1_max 264 + set in2_min 10.8 + set in2_max 13.2 + set fan1_min 1000 + set fan1_max 30000 + set temp1_max_hyst 60 + set temp1_max 70 + set temp2_max_hyst 60 + set temp2_max 70 + set power1_max 1222 + set power2_max 1100 + set curr1_min 0 + set curr1_max 7 + set curr2_min 0 + set curr2_max 90 + +chip "dps1100-i2c-*-59" + set in1_min 90 + set in1_max 264 + set in2_min 10.8 + set in2_max 13.2 + set fan1_min 1000 + set fan1_max 30000 + set temp1_max_hyst 60 + set temp1_max 70 + set temp2_max_hyst 60 + set temp2_max 70 + set power1_max 1222 + set power2_max 1100 + set curr1_min 0 + set curr1_max 7 + set curr2_min 0 + set curr2_max 90 + +# chip "ir38060-i2c-*-43" + +# chip "ir38062-i2c-*-49" + +# chip "ir3595-i2c-*-12" + +# chip "ir38060-i2c-*-47" + +# chip "ir3584-i2c-*-70" \ No newline at end of file diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/__init__.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/__init__.py new file mode 100644 index 000000000000..d82f3749319c --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/__init__.py @@ -0,0 +1,2 @@ +__all__ = ["platform", "chassis"] +from sonic_platform import * diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/chassis.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/chassis.py new file mode 100644 index 000000000000..6faf49c7f599 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/chassis.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Module contains an implementation of SONiC Platform Base API and +# provides the Chassis information which are available in the platform +# +############################################################################# + +try: + import sys + import re + import os + import subprocess + import json + from sonic_platform_base.chassis_base import ChassisBase + from sonic_platform.component import Component + from sonic_platform.eeprom import Eeprom + from sonic_platform.fan import Fan + from sonic_platform.sfp import Sfp + from sonic_platform.psu import Psu + from sonic_platform.thermal import Thermal + from helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +NUM_FAN_TRAY = 4 +NUM_FAN = 2 +NUM_PSU = 2 +NUM_THERMAL = 12 +NUM_SFP = 56 +NUM_COMPONENT = 7 +REBOOT_CAUSE_REG = "0xA106" +TLV_EEPROM_I2C_BUS = 0 +TLV_EEPROM_I2C_ADDR = 56 + +BASE_CPLD_PLATFORM = "questone2bd.cpldb" +BASE_GETREG_PATH = "/sys/devices/platform/{}/getreg".format(BASE_CPLD_PLATFORM) + + +class Chassis(ChassisBase): + """Platform-specific Chassis class""" + + def __init__(self): + ChassisBase.__init__(self) + self._eeprom = Eeprom(TLV_EEPROM_I2C_BUS, TLV_EEPROM_I2C_ADDR) + self._api_helper = APIHelper() + + for index in range(0, NUM_PSU): + psu = Psu(index) + self._psu_list.append(psu) + + for fant_index in range(0, NUM_FAN_TRAY): + for fan_index in range(0, NUM_FAN): + fan = Fan(fant_index, fan_index) + self._fan_list.append(fan) + + for index in range(0, NUM_SFP): + sfp = Sfp(index) + self._sfp_list.append(sfp) + + for index in range(0, NUM_COMPONENT): + component = Component(index) + self._component_list.append(component) + + for index in range(0, NUM_THERMAL): + thermal = Thermal(index) + self._thermal_list.append(thermal) + + def get_base_mac(self): + """ + Retrieves the base MAC address for the chassis + Returns: + A string containing the MAC address in the format + 'XX:XX:XX:XX:XX:XX' + """ + return self._eeprom.get_mac() + + def get_serial_number(self): + """ + Retrieves the hardware serial number for the chassis + Returns: + A string containing the hardware serial number for this chassis. + """ + return self._eeprom.get_serial() + + 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._eeprom.get_eeprom() + + def get_reboot_cause(self): + """ + Retrieves the cause of the previous reboot + + Returns: + A tuple (string, string) where the first element is a string + containing the cause of the previous reboot. This string must be + one of the predefined strings in this class. If the first string + is "REBOOT_CAUSE_HARDWARE_OTHER", the second string can be used + to pass a description of the reboot cause. + """ + + raw_cause = self._api_helper.get_register_value( + BASE_GETREG_PATH, REBOOT_CAUSE_REG) + hx_cause = raw_cause.lower() + reboot_cause = { + "0x00": self.REBOOT_CAUSE_HARDWARE_OTHER, + "0x11": self.REBOOT_CAUSE_POWER_LOSS, + "0x22": self.REBOOT_CAUSE_NON_HARDWARE, + "0x33": self.REBOOT_CAUSE_HARDWARE_OTHER, + "0x44": self.REBOOT_CAUSE_NON_HARDWARE, + "0x55": self.REBOOT_CAUSE_NON_HARDWARE, + "0x66": self.REBOOT_CAUSE_WATCHDOG, + "0x77": self.REBOOT_CAUSE_NON_HARDWARE + }.get(hx_cause, self.REBOOT_CAUSE_HARDWARE_OTHER) + + description = { + "0x00": "Unknown reason", + "0x11": "The last reset is Power on reset", + "0x22": "The last reset is soft-set CPU warm reset", + "0x33": "The last reset is soft-set CPU cold reset", + "0x44": "The last reset is CPU warm reset", + "0x55": "The last reset is CPU cold reset", + "0x66": "The last reset is watchdog reset", + "0x77": "The last reset is power cycle reset" + }.get(hx_cause, "Unknown reason") + + return (reboot_cause, description) + + def get_sfp(self, index): + """ + Retrieves sfp represented by (1-based) index + Args: + index: An integer, the index (1-based) of the sfp to retrieve. + The index should be the sequence of a physical port in a chassis, + starting from 1. + For example, 1 for Ethernet0, 2 for Ethernet4 and so on. + Returns: + An object dervied from SfpBase representing the specified sfp + """ + sfp = None + + try: + # The index will start from 1 + sfp = self._sfp_list[index-1] + except IndexError: + sys.stderr.write("SFP index {} out of range (1-{})\n".format( + index, len(self._sfp_list))) + return sfp + + def get_watchdog(self): + """ + Retreives hardware watchdog device on this chassis + Returns: + An object derived from WatchdogBase representing the hardware + watchdog device + """ + if self._watchdog is None: + from sonic_platform.watchdog import Watchdog + self._watchdog = Watchdog() + + return self._watchdog + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + return self._eeprom.get_product() + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + return True + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + return self._eeprom.get_pn() + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + return self.get_serial_number() + + 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 True diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/component.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/component.py new file mode 100644 index 000000000000..914c8136256f --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/component.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Component contains an implementation of SONiC Platform Base API and +# provides the components firmware management function +# +############################################################################# + +import json +import os.path + +try: + from sonic_platform_base.component_base import ComponentBase + from helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +COMPONENT_LIST = [ + ("BIOS", "Basic input/output System"), + ("BASE_CPLD", "Base board CPLD"), + ("FAN_CPLD", "Fan board CPLD"), + ("CPU_CPLD", "CPU board CPLD"), + ("SW_CPLD1", "Switch board CPLD 1"), + ("SW_CPLD2", "Switch board CPLD 2"), + ("FPGA", "Field-programmable gate array") +] +NAME_INDEX = 0 +DESCRIPTION_INDEX = 1 + +BASE_CPLD_PLATFORM = "questone2bd.cpldb" +SW_CPLD_PLATFORM = "questone2bd.switchboard" +PLATFORM_SYSFS_PATH = "/sys/devices/platform/" + +FPGA_GETREG_PATH = "{}/{}/FPGA/getreg".format( + PLATFORM_SYSFS_PATH, SW_CPLD_PLATFORM) +BASE_GETREG_PATH = "{}/{}/getreg".format( + PLATFORM_SYSFS_PATH, BASE_CPLD_PLATFORM) +SW_CPLD1_GETREG_PATH = "{}/{}/CPLD1/getreg".format( + PLATFORM_SYSFS_PATH, SW_CPLD_PLATFORM) +SW_CPLD2_GETREG_PATH = "{}/{}/CPLD2/getreg".format( + PLATFORM_SYSFS_PATH, SW_CPLD_PLATFORM) +BIOS_VER_PATH = "/sys/class/dmi/id/bios_version" +FAN_CPLD_VER_PATH = "/sys/bus/i2c/drivers/fancpld/66-000d/version" + +BASE_CPLD_VER_REG = "0xA100" +CPU_CPLD_VER_REG = "0xA1E0" +SW_CPLD_VER_REG = "0x00" +FPGA_VER_REG = "0x00" + +UNKNOWN_VER = "Unknown" +FPGA_UPGRADE_CMD = "fpga_prog {}" +CPLD_UPGRADE_CMD = "ispvm {}" + + +class Component(ComponentBase): + """Platform-specific Component class""" + + DEVICE_TYPE = "component" + + def __init__(self, component_index): + ComponentBase.__init__(self) + self.index = component_index + self.name = self.get_name() + self._api_helper = APIHelper() + + def __get_fpga_ver(self): + version_raw = self._api_helper.get_register_value( + FPGA_GETREG_PATH, '0x00') + return "{}.{}".format(int(version_raw[2:][:4], 16), int(version_raw[2:][4:], 16)) if version_raw else UNKNOWN_VER + + def __get_cpld_ver(self): + cpld_version_dict = dict() + cpld_ver_info = { + 'BASE_CPLD': self._api_helper.get_register_value(BASE_GETREG_PATH, BASE_CPLD_VER_REG), + 'CPU_CPLD': self._api_helper.get_register_value(BASE_GETREG_PATH, CPU_CPLD_VER_REG), + 'SW_CPLD1': self._api_helper.get_register_value(SW_CPLD1_GETREG_PATH, SW_CPLD_VER_REG), + 'SW_CPLD2': self._api_helper.get_register_value(SW_CPLD2_GETREG_PATH, SW_CPLD_VER_REG), + } + for cpld_name, cpld_ver in cpld_ver_info.items(): + cpld_ver_str = "{}.{}".format(int(cpld_ver[2], 16), int( + cpld_ver[3], 16)) if cpld_ver else UNKNOWN_VER + cpld_version_dict[cpld_name] = cpld_ver_str + + fan_cpld = self._api_helper.read_one_line_file(FAN_CPLD_VER_PATH) + cpld_version_dict['FAN_CPLD'] = float( + int(fan_cpld, 16)) if fan_cpld else UNKNOWN_VER + + return cpld_version_dict + + def get_name(self): + """ + Retrieves the name of the component + Returns: + A string containing the name of the component + """ + return COMPONENT_LIST[self.index][NAME_INDEX] + + def get_description(self): + """ + Retrieves the description of the component + Returns: + A string containing the description of the component + """ + return COMPONENT_LIST[self.index][DESCRIPTION_INDEX] + + def get_firmware_version(self): + """ + Retrieves the firmware version of module + Returns: + string: The firmware versions of the module + """ + fw_version_info = { + 'BIOS': self._api_helper.read_one_line_file(BIOS_VER_PATH), + 'FPGA': self.__get_fpga_ver(), + } + fw_version_info.update(self.__get_cpld_ver()) + return fw_version_info.get(self.name, UNKNOWN_VER) + + def install_firmware(self, image_path): + """ + Install firmware to module + Args: + image_path: A string, path to firmware image + Returns: + A boolean, True if install successfully, False if not + """ + + install_command = { + "BASE_CPLD": CPLD_UPGRADE_CMD.format(image_path), + "FAN_CPLD": CPLD_UPGRADE_CMD.format(image_path), + "CPU_CPLD": CPLD_UPGRADE_CMD.format(image_path), + "SW_CPLD1": CPLD_UPGRADE_CMD.format(image_path), + "SW_CPLD2": CPLD_UPGRADE_CMD.format(image_path), + "FPGA": FPGA_UPGRADE_CMD.format(image_path), + }.get(self.name, None) + + if not os.path.isfile(str(image_path)) or (install_command is None) or (not self._api_helper.is_host()): + return False + + status = self._api_helper.run_interactive_command(install_command) + return status diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/eeprom.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/eeprom.py new file mode 100644 index 000000000000..b4d782904126 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/eeprom.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Platform and model specific eeprom subclass, inherits from the base class, +# and provides the followings: +# - the eeprom format definition +# - specific encoder/decoder if there is special need +############################################################################# + +try: + import glob + import os + import sys + import imp + import re + from array import array + from cStringIO import StringIO + from sonic_platform_base.sonic_eeprom import eeprom_dts + from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo +except ImportError, e: + raise ImportError(str(e) + "- required module not found") + +CACHE_ROOT = '/var/cache/sonic/decode-syseeprom' +CACHE_FILE = 'syseeprom_cache' + + +class Eeprom(eeprom_tlvinfo.TlvInfoDecoder): + + EEPROM_DECODE_HEADLINES = 6 + + def __init__(self, i2c, reg): + self._eeprom_path = "/sys/class/i2c-adapter/i2c-{0}/{0}-00{1}/eeprom".format(i2c, reg) + super(Eeprom, self).__init__(self._eeprom_path, 0, '', True) + self._eeprom = self._load_eeprom() + + def __parse_output(self, decode_output): + decode_output.replace('\0', '') + lines = decode_output.split('\n') + lines = lines[self.EEPROM_DECODE_HEADLINES:] + _eeprom_info_dict = dict() + + for line in lines: + try: + match = re.search( + '(0x[0-9a-fA-F]{2})([\s]+[\S]+[\s]+)([\S]+)', line) + if match is not None: + idx = match.group(1) + value = match.group(3).rstrip('\0') + + _eeprom_info_dict[idx] = value + except: + pass + return _eeprom_info_dict + + def _load_eeprom(self): + original_stdout = sys.stdout + sys.stdout = StringIO() + try: + self.read_eeprom_db() + except: + decode_output = sys.stdout.getvalue() + sys.stdout = original_stdout + return self.__parse_output(decode_output) + + status = self.check_status() + if 'ok' not in status: + return False + + if not os.path.exists(CACHE_ROOT): + try: + os.makedirs(CACHE_ROOT) + except: + pass + + # + # only the eeprom classes that inherit from eeprom_base + # support caching. Others will work normally + # + try: + self.set_cache_name(os.path.join(CACHE_ROOT, CACHE_FILE)) + except: + pass + + e = self.read_eeprom() + if e is None: + return 0 + + try: + self.update_cache(e) + except: + pass + + self.decode_eeprom(e) + decode_output = sys.stdout.getvalue() + sys.stdout = original_stdout + + (is_valid, valid_crc) = self.is_checksum_valid(e) + if not is_valid: + return False + + return self.__parse_output(decode_output) + + def get_eeprom(self): + return self._eeprom + + def get_product(self): + return self._eeprom.get('0x21', "Undefined.") + + def get_pn(self): + return self._eeprom.get('0x22', "Undefined.") + + def get_serial(self): + return self._eeprom.get('0x23', "Undefined.") + + def get_mac(self): + return self._eeprom.get('0x24', "Undefined.") diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/fan.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/fan.py new file mode 100644 index 000000000000..6c060e852d47 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/fan.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python + +############################################################################# +# 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 + from helper import APIHelper +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_SYSFS_PATH = "/sys/bus/i2c/drivers/fancpld/66-000d/" +FAN_DIRECTION_BIT = 1 +FAN_PRESENT_BIT = 1 +FAN_RPM_MULTIPLIER = 150 +FAN_MAX_PWM = 255 +FAN_MAX_RPM = FAN_MAX_PWM * FAN_RPM_MULTIPLIER +FAN_PRESENT_SYSFS = "fan{}_present" +FAN_PWM_SYSFS = "fan{}_pwm" +FAN_DIRECTION_SYSFS = "fan{}_dir" +FAN_LED_SYSFS = "fan{}_led" +FAN_LED_GREEN_CMD = "0x1" +FAN_LED_RED_CMD = "0x2" +FAN_LED_OFF_CMD = "0x3" + +PSU_FAN_MAX_RPM = 30000 +PSU_HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon" +PSU_I2C_MAPPING = { + 0: { + "i2c_num": 76, + "pmbus_reg": "59", + }, + 1: { + "i2c_num": 75, + "pmbus_reg": "58", + }, +} + +FAN_MUX_HWMON_PATH = "/sys/bus/i2c/devices/i2c-66/i2c-{0}/{0}-0050/" +PSU_MUX_HWMON_PATH = "/sys/bus/i2c/devices/i2c-68/i2c-{0}/{0}-0050/" + + +class Fan(FanBase): + """Platform-specific Fan class""" + + def __init__(self, fan_tray_index, fan_index=0, is_psu_fan=False, psu_index=0): + 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.psu_hwmon_path = PSU_HWMON_PATH.format( + PSU_I2C_MAPPING[self.psu_index]["i2c_num"], PSU_I2C_MAPPING[self.psu_index]["pmbus_reg"]) + self._api_helper = APIHelper() + self.index = self.fan_tray_index * 2 + self.fan_index + + def __read_fan_sysfs(self, sysfs_file): + sysfs_path = os.path.join(FAN_SYSFS_PATH, sysfs_file) + return self._api_helper.read_one_line_file(sysfs_path) + + def __write_fan_sysfs(self, sysfs_file, value): + sysfs_path = os.path.join(FAN_SYSFS_PATH, sysfs_file) + return self._api_helper.write_file(sysfs_path, value) + + def __search_dirpath_contain(self, directory, search_str, file_start): + for dirpath, dirnames, files in os.walk(directory): + for name in files: + file_path = os.path.join(dirpath, name) + if name.startswith(file_start) and search_str in self._api_helper.read_txt_file(file_path): + return dirpath + return None + + def get_direction(self): + """ + Retrieves the direction of fan + Returns: + A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST + depending on fan direction + """ + if self.is_psu_fan: + return self.FAN_DIRECTION_EXHAUST + + fan_direction_val = self.__read_fan_sysfs( + FAN_DIRECTION_SYSFS.format(self.fan_tray_index+1)) + + return self.FAN_DIRECTION_EXHAUST if fan_direction_val == "0x0" else self.FAN_DIRECTION_INTAKE + + 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 = 38250 RPM + """ + if self.is_psu_fan: + speed = 0 + fan_name = "fan{}_input" + label = "dps1100" + + hwmon = self.__search_dirpath_contain( + self.psu_hwmon_path, label, "name") + if hwmon: + fan_path = os.path.join( + hwmon, fan_name.format(self.fan_index+1)) + speed_rpm = self._api_helper.read_one_line_file(fan_path) + speed = int(float(speed_rpm) / PSU_FAN_MAX_RPM * 100) + + return speed + + speed_raw = self.__read_fan_sysfs( + FAN_PWM_SYSFS.format(self.fan_tray_index+1)) + speed_val = int(speed_raw, 16) + speed = int(float(speed_val)/FAN_MAX_PWM * 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 10 + + 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: systemctl stop fanctrl.service + auto: systemctl start fanctrl.service + """ + + if self.is_psu_fan: + # Not support + return False + + speed_hex = hex(int(float(speed)/100 * 255)) + return self.__write_fan_sysfs(FAN_PWM_SYSFS.format(self.fan_tray_index+1), speed_hex) + + 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 + """ + + if self.is_psu_fan: + # Not support + return False + + 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) + + return self.__write_fan_sysfs(FAN_LED_SYSFS.format(self.fan_tray_index+1), led_cmd) + + 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: + Output + STATUS_LED_COLOR_GREEN = "green" + STATUS_LED_COLOR_AMBER = "amber" + STATUS_LED_COLOR_RED = "red" + STATUS_LED_COLOR_OFF = "off" + + Input + 0x1: green + 0x2: red + 0x3: off + """ + if self.is_psu_fan: + # Not support + return self.STATUS_LED_COLOR_OFF + + fan_led_raw = self.__read_fan_sysfs( + FAN_LED_SYSFS.format(self.fan_tray_index+1)) + + fan_status_led = { + "0x1": self.STATUS_LED_COLOR_GREEN, + "0x2": self.STATUS_LED_COLOR_RED, + "0x3": self.STATUS_LED_COLOR_OFF + }.get(fan_led_raw, self.STATUS_LED_COLOR_OFF) + + return fan_status_led + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + 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 + """ + if self.is_psu_fan: + return True + + fan_presence_bit_val = "0" + + return True if fan_presence_bit_val == "0" else False + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + if self.is_psu_fan: + temp_file = PSU_MUX_HWMON_PATH.format((self.fan_tray_index+1) + 75) + return self._api_helper.fru_decode_product_model(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + temp_file = FAN_MUX_HWMON_PATH.format((self.fan_tray_index+1) * 2) + return self._api_helper.fru_decode_product_model(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + if self.is_psu_fan: + temp_file = PSU_MUX_HWMON_PATH.format((self.fan_tray_index+1) + 75) + return self._api_helper.fru_decode_product_serial(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + temp_file = FAN_MUX_HWMON_PATH.format((self.fan_tray_index+1) * 2) + return self._api_helper.fru_decode_product_serial(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + 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 diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/helper.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/helper.py new file mode 100644 index 000000000000..acc7af0424b6 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/helper.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python + +import os +import struct +import subprocess +from mmap import * +from sonic_daemon_base.daemon_base import DaemonBase + +SCALE = 16 +BIN_BITS = 8 +EMPTY_STRING = "" +HOST_CHK_CMD = "docker > /dev/null 2>&1" + + +class APIHelper(): + + def __init__(self): + (self.platform, self.hwsku) = DaemonBase().get_platform_and_hwsku() + + def get_register_value(self, getreg_path, register): + cmd = "echo {1} > {0}; cat {0}".format(getreg_path, register) + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + return raw_data.strip() if not err else None + + def hex_to_bin(self, ini_string): + return bin(int(ini_string, SCALE)).zfill(BIN_BITS) + + def is_host(self): + return os.system(HOST_CHK_CMD) == 0 + + def pci_get_value(self, resource, offset): + status = True + result = "" + try: + fd = os.open(resource, os.O_RDWR) + mm = mmap(fd, 0) + mm.seek(int(offset)) + read_data_stream = mm.read(4) + result = struct.unpack('I', read_data_stream) + except: + status = False + return status, result + + def run_command(self, cmd): + status = True + result = "" + try: + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + except: + status = False + return status, result + + def run_interactive_command(self, cmd): + try: + os.system(cmd) + except: + return False + return True + + def read_txt_file(self, file_path): + try: + with open(file_path, 'r') as fd: + data = fd.read() + return data.strip() + except IOError: + pass + return None + + def read_one_line_file(self, file_path): + try: + with open(file_path, 'r') as fd: + data = fd.readline() + return data.strip() + except IOError: + pass + return None + + def search_file_by_contain(self, directory, search_str, file_start): + for dirpath, dirnames, files in os.walk(directory): + for name in files: + file_path = os.path.join(dirpath, name) + if name.startswith(file_start) and search_str in self._api_helper.read_txt_file(file_path): + return dirpath + return None + + def write_file(self, file_path, data): + try: + with open(file_path, 'w') as fd: + fd.write(str(data)) + return True + except: + pass + return False + + def ipmi_raw(self, netfn, cmd): + status = True + result = "" + try: + cmd = "ipmitool raw {} {}".format(str(netfn), str(cmd)) + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except: + status = False + return status, result + + def ipmi_fru_id(self, id, key=None): + status = True + result = "" + try: + cmd = "ipmitool fru print {}".format(str( + id)) if not key else "ipmitool fru print {0} | grep '{1}' ".format(str(id), str(key)) + + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except: + status = False + return status, result + + def ipmi_set_ss_thres(self, id, threshold_key, value): + status = True + result = "" + try: + cmd = "ipmitool sensor thresh '{}' {} {}".format( + str(id), str(threshold_key), str(value)) + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + raw_data, err = p.communicate() + if err == '': + result = raw_data.strip() + else: + status = False + except: + status = False + return status, result + + def fru_decode_product_serial(self, data): + if data[4] != 00: + start_product_info = ord(data[4]) * 8 + start_format_version = start_product_info + start_product_info = start_format_version + 1 + start_product_Lang_code = start_product_info + 1 + start_product_Manu_name = start_product_Lang_code + 1 + start_product_Manu_name_length = ord(data[start_product_Manu_name]) & 0x0F + start_product_name = start_product_Manu_name + start_product_Manu_name_length + 1 + start_product_name_length = ord(data[start_product_name]) & 0x0F + start_product_module_number = start_product_name + start_product_name_length +1 + start_product_module_number_length = ord(data[start_product_module_number]) & 0x0F + start_product_version = start_product_module_number + start_product_module_number_length +1 + start_product_version_length = ord(data[start_product_version]) & 0x0F + start_product_serial_number = start_product_version + start_product_version_length +1 + start_product_serial_number_length = ord(data[start_product_serial_number]) & 0x1F + return data[start_product_serial_number+1:start_product_serial_number+start_product_serial_number_length+1] + else: + return None + + def fru_decode_product_model(self, data): + if data[4] != 00: + start_product_info = ord(data[4]) * 8 + start_format_version = start_product_info + start_product_info = start_format_version + 1 + start_product_Lang_code = start_product_info + 1 + start_product_Manu_name = start_product_Lang_code + 1 + start_product_Manu_name_length = ord(data[start_product_Manu_name]) & 0x0F + start_product_name = start_product_Manu_name + start_product_Manu_name_length + 1 + start_product_name_length = ord(data[start_product_name]) & 0x0F + start_product_module_number = start_product_name + start_product_name_length +1 + start_product_module_number_length = ord(data[start_product_module_number]) & 0x0F + start_product_version = start_product_module_number + start_product_module_number_length +1 + start_product_version_length = ord(data[start_product_version]) & 0x0F + start_product_serial_number = start_product_version + start_product_version_length +1 + start_product_serial_number_length = ord(data[start_product_serial_number]) & 0x1F + return data[start_product_module_number+1:start_product_module_number+start_product_module_number_length+1] + else: + return None + + def read_eeprom_sysfs(self,sys_path,sysfs_file): + sysfs_path = os.path.join(sys_path, sysfs_file) + try: + with open(sysfs_path, 'rb') as fd: + data = fd.read() + return data + except: + pass + return False \ No newline at end of file diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/platform.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/platform.py new file mode 100644 index 000000000000..a632de87e742 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/platform.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Module contains an implementation of SONiC Platform Base API and +# provides the platform information +# +############################################################################# + +try: + from sonic_platform_base.platform_base import PlatformBase + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + + +class Platform(PlatformBase): + """Platform-specific Platform class""" + + def __init__(self): + PlatformBase.__init__(self) + self._chassis = Chassis() diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/psu.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/psu.py new file mode 100644 index 000000000000..7e1584496cdd --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/psu.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Module contains an implementation of SONiC Platform Base API and +# provides the PSUs status which are available in the platform +# +############################################################################# + +try: + import os + import re + import math + from sonic_platform_base.psu_base import PsuBase + from sonic_platform.eeprom import Eeprom + from sonic_platform.fan import Fan + from helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +PSU_NUM_FAN = [1, 1] + +PRESENT_BIT = '0' +POWER_OK_BIT = '1' +PSU_INFO_MAPPING = { + 0: { + "name": "PSU-1", + "status": 0, + "present": 2, + "i2c_num": 76, + "pmbus_reg": "59", + "eeprom_reg": "51", + }, + 1: { + "name": "PSU-2", + "status": 1, + "present": 3, + "i2c_num": 75, + "pmbus_reg": "58", + "eeprom_reg": "50" + }, +} +PSU_STATUS_REGISTER = "0xA160" +HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}/hwmon" +PSU_POWER_DIVIDER = 1000000 +PSU_VOLT_DIVIDER = 1000 +PSU_CUR_DIVIDER = 1000 + +PSU_MUX_HWMON_PATH = "/sys/bus/i2c/devices/i2c-68/i2c-{0}/{0}-00{1}/" +BASE_CPLD_PLATFORM = "questone2bd.cpldb" +BASE_GETREG_PATH = "/sys/devices/platform/{}/getreg".format(BASE_CPLD_PLATFORM) + + +class Psu(PsuBase): + """Platform-specific Psu class""" + + def __init__(self, psu_index): + PsuBase.__init__(self) + self.index = psu_index + for fan_index in range(0, PSU_NUM_FAN[self.index]): + fan = Fan(fan_index, 0, is_psu_fan=True, psu_index=self.index) + self._fan_list.append(fan) + self.hwmon_path = HWMON_PATH.format( + PSU_INFO_MAPPING[self.index]["i2c_num"], PSU_INFO_MAPPING[self.index]["pmbus_reg"]) + self._api_helper = APIHelper() + + def __search_file_by_contain(self, directory, search_str, file_start): + for dirpath, dirnames, files in os.walk(directory): + for name in files: + file_path = os.path.join(dirpath, name) + if name.startswith(file_start) and search_str in self._api_helper.read_txt_file(file_path): + return file_path + return None + + def __get_psu_status(self): + psu_status_raw = self._api_helper.get_register_value( + BASE_GETREG_PATH, PSU_STATUS_REGISTER) + psu_status_bin = self._api_helper.hex_to_bin(psu_status_raw) + return str(psu_status_bin)[2:][::-1] + + def get_voltage(self): + """ + Retrieves current PSU voltage output + Returns: + A float number, the output voltage in volts, + e.g. 12.1 + """ + psu_voltage = 0.0 + voltage_name = "in{}_input" + voltage_label = "vout1" + + vout_label_path = self.__search_file_by_contain( + self.hwmon_path, voltage_label, "in") + if vout_label_path: + dir_name = os.path.dirname(vout_label_path) + basename = os.path.basename(vout_label_path) + in_num = filter(str.isdigit, basename) + vout_path = os.path.join( + dir_name, voltage_name.format(in_num)) + vout_val = self._api_helper.read_txt_file(vout_path) + psu_voltage = float(vout_val) / PSU_VOLT_DIVIDER + + return psu_voltage + + def get_current(self): + """ + Retrieves present electric current supplied by PSU + Returns: + A float number, the electric current in amperes, e.g 15.4 + """ + psu_current = 0.0 + current_name = "curr{}_input" + current_label = "iout1" + + curr_label_path = self.__search_file_by_contain( + self.hwmon_path, current_label, "cur") + if curr_label_path: + dir_name = os.path.dirname(curr_label_path) + basename = os.path.basename(curr_label_path) + cur_num = filter(str.isdigit, basename) + cur_path = os.path.join( + dir_name, current_name.format(cur_num)) + cur_val = self._api_helper.read_txt_file(cur_path) + psu_current = float(cur_val) / PSU_CUR_DIVIDER + + 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 = 0.0 + power_name = "power{}_input" + power_label = "pout1" + + pw_label_path = self.__search_file_by_contain( + self.hwmon_path, power_label, "power") + if pw_label_path: + dir_name = os.path.dirname(pw_label_path) + basename = os.path.basename(pw_label_path) + pw_num = filter(str.isdigit, basename) + pw_path = os.path.join( + dir_name, power_name.format(pw_num)) + pw_val = self._api_helper.read_txt_file(pw_path) + psu_power = float(pw_val) / PSU_POWER_DIVIDER + + 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. + """ + return self.get_status() + + 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 + Note: Only support green and off + Returns: + bool: True if status LED state is set successfully, False if not + """ + # NOT ALLOW + + return False + + def get_status_led(self): + """ + Gets the state of the PSU status LED + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings above + """ + return self.STATUS_LED_COLOR_OFF + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + return PSU_INFO_MAPPING[self.index]["name"] + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + psu_stat_raw = self.__get_psu_status() + psu_presence = psu_stat_raw[PSU_INFO_MAPPING[self.index]["present"]] + + return psu_presence == PRESENT_BIT + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + temp_file = PSU_MUX_HWMON_PATH.format( + ((self.index) + 75), self.index+50) + return self._api_helper.fru_decode_product_model(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + temp_file = PSU_MUX_HWMON_PATH.format( + ((self.index) + 75), self.index+50) + return self._api_helper.fru_decode_product_serial(self._api_helper.read_eeprom_sysfs(temp_file, "eeprom")) + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + psu_stat_raw = self.__get_psu_status() + psu_status = psu_stat_raw[PSU_INFO_MAPPING[self.index]["status"]] + + return psu_status == POWER_OK_BIT diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/sfp.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/sfp.py new file mode 100644 index 000000000000..8d415410a505 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/sfp.py @@ -0,0 +1,1311 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Sfp contains an implementation of SONiC Platform Base API and +# provides the sfp device status which are available in the platform +# +############################################################################# + +import os +import time +import subprocess +import sonic_device_util +from ctypes import create_string_buffer + +try: + from sonic_platform_base.sfp_base import SfpBase + from sonic_platform_base.sonic_eeprom import eeprom_dts + from sonic_platform_base.sonic_sfp.sff8472 import sff8472InterfaceId + from sonic_platform_base.sonic_sfp.sff8472 import sff8472Dom + from sonic_platform_base.sonic_sfp.sff8436 import sff8436InterfaceId + from sonic_platform_base.sonic_sfp.sff8436 import sff8436Dom + from sonic_platform_base.sonic_sfp.inf8628 import inf8628InterfaceId + from sonic_platform_base.sonic_sfp.sfputilhelper import SfpUtilHelper + from helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +INFO_OFFSET = 128 +DOM_OFFSET = 0 + +# definitions of the offset and width for values in XCVR info eeprom +XCVR_INTFACE_BULK_OFFSET = 0 +XCVR_INTFACE_BULK_WIDTH_QSFP = 20 +XCVR_INTFACE_BULK_WIDTH_SFP = 21 +XCVR_TYPE_OFFSET = 0 +XCVR_TYPE_WIDTH = 1 +XCVR_EXT_TYPE_OFFSET = 1 +XCVR_EXT_TYPE_WIDTH = 1 +XCVR_CONNECTOR_OFFSET = 2 +XCVR_CONNECTOR_WIDTH = 1 +XCVR_COMPLIANCE_CODE_OFFSET = 3 +XCVR_COMPLIANCE_CODE_WIDTH = 8 +XCVR_ENCODING_OFFSET = 11 +XCVR_ENCODING_WIDTH = 1 +XCVR_NBR_OFFSET = 12 +XCVR_NBR_WIDTH = 1 +XCVR_EXT_RATE_SEL_OFFSET = 13 +XCVR_EXT_RATE_SEL_WIDTH = 1 +XCVR_CABLE_LENGTH_OFFSET = 14 +XCVR_CABLE_LENGTH_WIDTH_QSFP = 5 +XCVR_CABLE_LENGTH_WIDTH_SFP = 6 +XCVR_VENDOR_NAME_OFFSET = 20 +XCVR_VENDOR_NAME_WIDTH = 16 +XCVR_VENDOR_OUI_OFFSET = 37 +XCVR_VENDOR_OUI_WIDTH = 3 +XCVR_VENDOR_PN_OFFSET = 40 +XCVR_VENDOR_PN_WIDTH = 16 +XCVR_HW_REV_OFFSET = 56 +XCVR_HW_REV_WIDTH_OSFP = 2 +XCVR_HW_REV_WIDTH_QSFP = 2 +XCVR_HW_REV_WIDTH_SFP = 4 +XCVR_VENDOR_SN_OFFSET = 68 +XCVR_VENDOR_SN_WIDTH = 16 +XCVR_VENDOR_DATE_OFFSET = 84 +XCVR_VENDOR_DATE_WIDTH = 8 +XCVR_DOM_CAPABILITY_OFFSET = 92 +XCVR_DOM_CAPABILITY_WIDTH = 2 + +XCVR_INTERFACE_DATA_START = 0 +XCVR_INTERFACE_DATA_SIZE = 92 + +QSFP_DOM_BULK_DATA_START = 22 +QSFP_DOM_BULK_DATA_SIZE = 36 +SFP_DOM_BULK_DATA_START = 96 +SFP_DOM_BULK_DATA_SIZE = 10 + +# definitions of the offset for values in OSFP info eeprom +OSFP_TYPE_OFFSET = 0 +OSFP_VENDOR_NAME_OFFSET = 129 +OSFP_VENDOR_PN_OFFSET = 148 +OSFP_HW_REV_OFFSET = 164 +OSFP_VENDOR_SN_OFFSET = 166 + +# Offset for values in QSFP eeprom +QSFP_DOM_REV_OFFSET = 1 +QSFP_DOM_REV_WIDTH = 1 +QSFP_TEMPE_OFFSET = 22 +QSFP_TEMPE_WIDTH = 2 +QSFP_VOLT_OFFSET = 26 +QSFP_VOLT_WIDTH = 2 +QSFP_VERSION_COMPLIANCE_OFFSET = 1 +QSFP_VERSION_COMPLIANCE_WIDTH = 2 +QSFP_CHANNL_MON_OFFSET = 34 +QSFP_CHANNL_MON_WIDTH = 16 +QSFP_CHANNL_MON_WITH_TX_POWER_WIDTH = 24 +QSFP_CHANNL_DISABLE_STATUS_OFFSET = 86 +QSFP_CHANNL_DISABLE_STATUS_WIDTH = 1 +QSFP_CHANNL_RX_LOS_STATUS_OFFSET = 3 +QSFP_CHANNL_RX_LOS_STATUS_WIDTH = 1 +QSFP_CHANNL_TX_FAULT_STATUS_OFFSET = 4 +QSFP_CHANNL_TX_FAULT_STATUS_WIDTH = 1 +QSFP_CONTROL_OFFSET = 86 +QSFP_CONTROL_WIDTH = 8 +QSFP_MODULE_MONITOR_OFFSET = 0 +QSFP_MODULE_MONITOR_WIDTH = 9 +QSFP_POWEROVERRIDE_OFFSET = 93 +QSFP_POWEROVERRIDE_WIDTH = 1 +QSFP_POWEROVERRIDE_BIT = 0 +QSFP_POWERSET_BIT = 1 +QSFP_OPTION_VALUE_OFFSET = 192 +QSFP_OPTION_VALUE_WIDTH = 4 +QSFP_MODULE_UPPER_PAGE3_START = 384 +QSFP_MODULE_THRESHOLD_OFFSET = 128 +QSFP_MODULE_THRESHOLD_WIDTH = 24 +QSFP_CHANNL_THRESHOLD_OFFSET = 176 +QSFP_CHANNL_THRESHOLD_WIDTH = 24 + +SFP_MODULE_ADDRA2_OFFSET = 256 +SFP_MODULE_THRESHOLD_OFFSET = 0 +SFP_MODULE_THRESHOLD_WIDTH = 56 +SFP_CHANNL_THRESHOLD_OFFSET = 112 +SFP_CHANNL_THRESHOLD_WIDTH = 2 + +SFP_TEMPE_OFFSET = 96 +SFP_TEMPE_WIDTH = 2 +SFP_VOLT_OFFSET = 98 +SFP_VOLT_WIDTH = 2 +SFP_CHANNL_MON_OFFSET = 100 +SFP_CHANNL_MON_WIDTH = 6 +SFP_CHANNL_STATUS_OFFSET = 110 +SFP_CHANNL_STATUS_WIDTH = 1 + + +qsfp_cable_length_tup = ('Length(km)', 'Length OM3(2m)', + 'Length OM2(m)', 'Length OM1(m)', + 'Length Cable Assembly(m)') + +sfp_cable_length_tup = ('LengthSMFkm-UnitsOfKm', 'LengthSMF(UnitsOf100m)', + 'Length50um(UnitsOf10m)', 'Length62.5um(UnitsOfm)', + 'LengthCable(UnitsOfm)', 'LengthOM3(UnitsOf10m)') + +sfp_compliance_code_tup = ('10GEthernetComplianceCode', 'InfinibandComplianceCode', + 'ESCONComplianceCodes', 'SONETComplianceCodes', + 'EthernetComplianceCodes', 'FibreChannelLinkLength', + 'FibreChannelTechnology', 'SFP+CableTechnology', + 'FibreChannelTransmissionMedia', 'FibreChannelSpeed') + +qsfp_compliance_code_tup = ('10/40G Ethernet Compliance Code', 'SONET Compliance codes', + 'SAS/SATA compliance codes', 'Gigabit Ethernet Compliant codes', + 'Fibre Channel link length/Transmitter Technology', + 'Fibre Channel transmission media', 'Fibre Channel Speed') + +SFP_TYPE = "SFP" +QSFP_TYPE = "QSFP" +OSFP_TYPE = "OSFP" + +PORT_START = 1 +PORT_END = 56 +QSFP_PORT_START = 49 +QSFP_PORT_END = 56 +SFP_PORT_START = 1 +SFP_PORT_END = 48 + +PORT_INFO_PATH = '/sys/class/questone2bd_fpga' +SFP_I2C_START = 10 + + +class Sfp(SfpBase): + """Platform-specific Sfp class""" + + # Path to QSFP sysfs + PLATFORM_ROOT_PATH = "/usr/share/sonic/device" + PMON_HWSKU_PATH = "/usr/share/sonic/hwsku" + + def __init__(self, sfp_index): + SfpBase.__init__(self) + # Init index + self.index = sfp_index + self.port_num = self.index + 1 + self.dom_supported = False + self.sfp_type, self.port_name = self.__get_sfp_info() + self._api_helper = APIHelper() + self.platform = self._api_helper.platform + self.hwsku = self._api_helper.hwsku + + # Init eeprom path + eeprom_path = '/sys/bus/i2c/devices/i2c-{0}/{0}-0050/eeprom' + self.port_to_eeprom_mapping = {} + self.port_to_i2c_mapping = {} + + for x in range(PORT_START, PORT_END + 1): + self.port_to_i2c_mapping[x] = (SFP_I2C_START + x) - 1 + port_eeprom_path = eeprom_path.format(self.port_to_i2c_mapping[x]) + self.port_to_eeprom_mapping[x] = port_eeprom_path + + self.info_dict_keys = ['type', 'hardwarerev', 'serialnum', 'manufacturename', 'modelname', 'Connector', 'encoding', 'ext_identifier', + 'ext_rateselect_compliance', 'cable_type', 'cable_length', 'nominal_bit_rate', 'specification_compliance', 'vendor_date', 'vendor_oui'] + + self.dom_dict_keys = ['rx_los', 'tx_fault', 'reset_status', 'power_lpmode', 'tx_disable', 'tx_disable_channel', 'temperature', 'voltage', + 'rx1power', 'rx2power', 'rx3power', 'rx4power', 'tx1bias', 'tx2bias', 'tx3bias', 'tx4bias', 'tx1power', 'tx2power', 'tx3power', 'tx4power'] + + self.threshold_dict_keys = ['temphighalarm', 'temphighwarning', 'templowalarm', 'templowwarning', 'vcchighalarm', 'vcchighwarning', 'vcclowalarm', 'vcclowwarning', 'rxpowerhighalarm', 'rxpowerhighwarning', + 'rxpowerlowalarm', 'rxpowerlowwarning', 'txpowerhighalarm', 'txpowerhighwarning', 'txpowerlowalarm', 'txpowerlowwarning', 'txbiashighalarm', 'txbiashighwarning', 'txbiaslowalarm', 'txbiaslowwarning'] + + self._dom_capability_detect() + + def __get_sfp_info(self): + port_name = "Unknown" + sfp_type = "Unknown" + + if self.port_num >= QSFP_PORT_START and self.port_num <= QSFP_TYPE: + sfp_type = QSFP_TYPE + port_name = "QSFP" + str(self.port_num - QSFP_PORT_START + 1) + elif self.port_num >= SFP_PORT_START and self.port_num <= SFP_PORT_END: + sfp_type = SFP_TYPE + port_name = "SFP" + str(self.port_num - SFP_PORT_START + 1) + return sfp_type, port_name + + def __convert_string_to_num(self, value_str): + if "-inf" in value_str: + return 'N/A' + elif "Unknown" in value_str: + return 'N/A' + elif 'dBm' in value_str: + t_str = value_str.rstrip('dBm') + return float(t_str) + elif 'mA' in value_str: + t_str = value_str.rstrip('mA') + return float(t_str) + elif 'C' in value_str: + t_str = value_str.rstrip('C') + return float(t_str) + elif 'Volts' in value_str: + t_str = value_str.rstrip('Volts') + return float(t_str) + else: + return 'N/A' + + def __get_path_to_port_config_file(self): + platform_path = "/".join([self.PLATFORM_ROOT_PATH, self.platform]) + hwsku_path = "/".join([platform_path, self.hwsku] + ) if self._api_helper.is_host() else self.PMON_HWSKU_PATH + return "/".join([hwsku_path, "port_config.ini"]) + + def __read_eeprom_specific_bytes(self, offset, num_bytes): + sysfsfile_eeprom = None + eeprom_raw = [] + for i in range(0, num_bytes): + eeprom_raw.append("0x00") + + sysfs_sfp_i2c_client_eeprom_path = self.port_to_eeprom_mapping[self.port_num] + try: + sysfsfile_eeprom = open( + sysfs_sfp_i2c_client_eeprom_path, mode="rb", buffering=0) + sysfsfile_eeprom.seek(offset) + raw = sysfsfile_eeprom.read(num_bytes) + for n in range(0, num_bytes): + eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2) + except: + pass + finally: + if sysfsfile_eeprom: + sysfsfile_eeprom.close() + + return eeprom_raw + + def _dom_capability_detect(self): + if not self.get_presence(): + self.dom_supported = False + self.dom_temp_supported = False + self.dom_volt_supported = False + self.dom_rx_power_supported = False + self.dom_tx_power_supported = False + self.calibration = 0 + return + + if self.sfp_type == "QSFP": + self.calibration = 1 + sfpi_obj = sff8436InterfaceId() + if sfpi_obj is None: + self.dom_supported = False + offset = 128 + + # QSFP capability byte parse, through this byte can know whether it support tx_power or not. + # TODO: in the future when decided to migrate to support SFF-8636 instead of SFF-8436, + # need to add more code for determining the capability and version compliance + # in SFF-8636 dom capability definitions evolving with the versions. + qsfp_dom_capability_raw = self.__read_eeprom_specific_bytes( + (offset + XCVR_DOM_CAPABILITY_OFFSET), XCVR_DOM_CAPABILITY_WIDTH) + if qsfp_dom_capability_raw is not None: + qsfp_version_compliance_raw = self.__read_eeprom_specific_bytes( + QSFP_VERSION_COMPLIANCE_OFFSET, QSFP_VERSION_COMPLIANCE_WIDTH) + qsfp_version_compliance = int( + qsfp_version_compliance_raw[0], 16) + dom_capability = sfpi_obj.parse_qsfp_dom_capability( + qsfp_dom_capability_raw, 0) + if qsfp_version_compliance >= 0x08: + self.dom_temp_supported = dom_capability['data']['Temp_support']['value'] == 'On' + self.dom_volt_supported = dom_capability['data']['Voltage_support']['value'] == 'On' + self.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On' + self.dom_tx_power_supported = dom_capability['data']['Tx_power_support']['value'] == 'On' + else: + self.dom_temp_supported = True + self.dom_volt_supported = True + self.dom_rx_power_supported = dom_capability['data']['Rx_power_support']['value'] == 'On' + self.dom_tx_power_supported = True + + self.dom_supported = True + self.calibration = 1 + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return None + qsfp_option_value_raw = self.__read_eeprom_specific_bytes( + QSFP_OPTION_VALUE_OFFSET, QSFP_OPTION_VALUE_WIDTH) + if qsfp_option_value_raw is not None: + optional_capability = sfpd_obj.parse_option_params( + qsfp_option_value_raw, 0) + self.dom_tx_disable_supported = optional_capability[ + 'data']['TxDisable']['value'] == 'On' + dom_status_indicator = sfpd_obj.parse_dom_status_indicator( + qsfp_version_compliance_raw, 1) + self.qsfp_page3_available = dom_status_indicator['data']['FlatMem']['value'] == 'Off' + else: + self.dom_supported = False + self.dom_temp_supported = False + self.dom_volt_supported = False + self.dom_rx_power_supported = False + self.dom_tx_power_supported = False + self.calibration = 0 + self.qsfp_page3_available = False + + elif self.sfp_type == "SFP": + sfpi_obj = sff8472InterfaceId() + if sfpi_obj is None: + return None + sfp_dom_capability_raw = self.__read_eeprom_specific_bytes( + XCVR_DOM_CAPABILITY_OFFSET, XCVR_DOM_CAPABILITY_WIDTH) + if sfp_dom_capability_raw is not None: + sfp_dom_capability = int(sfp_dom_capability_raw[0], 16) + self.dom_supported = (sfp_dom_capability & 0x40 != 0) + if self.dom_supported: + self.dom_temp_supported = True + self.dom_volt_supported = True + self.dom_rx_power_supported = True + self.dom_tx_power_supported = True + if sfp_dom_capability & 0x20 != 0: + self.calibration = 1 + elif sfp_dom_capability & 0x10 != 0: + self.calibration = 2 + else: + self.calibration = 0 + else: + self.dom_temp_supported = False + self.dom_volt_supported = False + self.dom_rx_power_supported = False + self.dom_tx_power_supported = False + self.calibration = 0 + self.dom_tx_disable_supported = ( + int(sfp_dom_capability_raw[1], 16) & 0x40 != 0) + else: + self.dom_supported = False + self.dom_temp_supported = False + self.dom_volt_supported = False + self.dom_rx_power_supported = False + self.dom_tx_power_supported = False + + def get_transceiver_info(self): + """ + Retrieves transceiver info of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + type |1*255VCHAR |type of SFP + hardwarerev |1*255VCHAR |hardware version of SFP + serialnum |1*255VCHAR |serial number of the SFP + manufacturename |1*255VCHAR |SFP vendor name + modelname |1*255VCHAR |SFP model name + Connector |1*255VCHAR |connector information + encoding |1*255VCHAR |encoding information + ext_identifier |1*255VCHAR |extend identifier + ext_rateselect_compliance |1*255VCHAR |extended rateSelect compliance + cable_length |INT |cable length in m + nominal_bit_rate |INT |nominal bit rate by 100Mbs + specification_compliance |1*255VCHAR |specification compliance + vendor_date |1*255VCHAR |vendor date + vendor_oui |1*255VCHAR |vendor OUI + ======================================================================== + """ + compliance_code_dict = {} + transceiver_info_dict = dict.fromkeys(self.info_dict_keys, 'N/A') + if not self.get_presence(): + return transceiver_info_dict + + # ToDo: OSFP tranceiver info parsing not fully supported. + # in inf8628.py lack of some memory map definition + # will be implemented when the inf8628 memory map ready + if self.sfp_type == OSFP_TYPE: + offset = 0 + vendor_rev_width = XCVR_HW_REV_WIDTH_OSFP + + sfpi_obj = inf8628InterfaceId() + if sfpi_obj is None: + return None + + sfp_type_raw = self.__read_eeprom_specific_bytes( + (offset + OSFP_TYPE_OFFSET), XCVR_TYPE_WIDTH) + if sfp_type_raw is not None: + sfp_type_data = sfpi_obj.parse_sfp_type(sfp_type_raw, 0) + else: + return None + + sfp_vendor_name_raw = self.__read_eeprom_specific_bytes( + (offset + OSFP_VENDOR_NAME_OFFSET), XCVR_VENDOR_NAME_WIDTH) + if sfp_vendor_name_raw is not None: + sfp_vendor_name_data = sfpi_obj.parse_vendor_name( + sfp_vendor_name_raw, 0) + else: + return None + + sfp_vendor_pn_raw = self.__read_eeprom_specific_bytes( + (offset + OSFP_VENDOR_PN_OFFSET), XCVR_VENDOR_PN_WIDTH) + if sfp_vendor_pn_raw is not None: + sfp_vendor_pn_data = sfpi_obj.parse_vendor_pn( + sfp_vendor_pn_raw, 0) + else: + return None + + sfp_vendor_rev_raw = self.__read_eeprom_specific_bytes( + (offset + OSFP_HW_REV_OFFSET), vendor_rev_width) + if sfp_vendor_rev_raw is not None: + sfp_vendor_rev_data = sfpi_obj.parse_vendor_rev( + sfp_vendor_rev_raw, 0) + else: + return None + + sfp_vendor_sn_raw = self.__read_eeprom_specific_bytes( + (offset + OSFP_VENDOR_SN_OFFSET), XCVR_VENDOR_SN_WIDTH) + if sfp_vendor_sn_raw is not None: + sfp_vendor_sn_data = sfpi_obj.parse_vendor_sn( + sfp_vendor_sn_raw, 0) + else: + return None + + transceiver_info_dict['type'] = sfp_type_data['data']['type']['value'] + transceiver_info_dict['manufacturename'] = sfp_vendor_name_data['data']['Vendor Name']['value'] + transceiver_info_dict['modelname'] = sfp_vendor_pn_data['data']['Vendor PN']['value'] + transceiver_info_dict['hardwarerev'] = sfp_vendor_rev_data['data']['Vendor Rev']['value'] + transceiver_info_dict['serialnum'] = sfp_vendor_sn_data['data']['Vendor SN']['value'] + transceiver_info_dict['vendor_oui'] = 'N/A' + transceiver_info_dict['vendor_date'] = 'N/A' + transceiver_info_dict['Connector'] = 'N/A' + transceiver_info_dict['encoding'] = 'N/A' + transceiver_info_dict['ext_identifier'] = 'N/A' + transceiver_info_dict['ext_rateselect_compliance'] = 'N/A' + transceiver_info_dict['cable_type'] = 'N/A' + transceiver_info_dict['cable_length'] = 'N/A' + transceiver_info_dict['specification_compliance'] = '{}' + transceiver_info_dict['nominal_bit_rate'] = 'N/A' + + else: + if self.sfp_type == QSFP_TYPE: + offset = 128 + vendor_rev_width = XCVR_HW_REV_WIDTH_QSFP + cable_length_width = XCVR_CABLE_LENGTH_WIDTH_QSFP + interface_info_bulk_width = XCVR_INTFACE_BULK_WIDTH_QSFP + sfp_type = 'QSFP' + + sfpi_obj = sff8436InterfaceId() + if sfpi_obj is None: + print("Error: sfp_object open failed") + return None + + else: + offset = 0 + vendor_rev_width = XCVR_HW_REV_WIDTH_SFP + cable_length_width = XCVR_CABLE_LENGTH_WIDTH_SFP + interface_info_bulk_width = XCVR_INTFACE_BULK_WIDTH_SFP + sfp_type = 'SFP' + + sfpi_obj = sff8472InterfaceId() + if sfpi_obj is None: + print("Error: sfp_object open failed") + return None + sfp_interface_bulk_raw = self.__read_eeprom_specific_bytes( + offset + XCVR_INTERFACE_DATA_START, XCVR_INTERFACE_DATA_SIZE) + if sfp_interface_bulk_raw is None: + return None + + start = XCVR_INTFACE_BULK_OFFSET - XCVR_INTERFACE_DATA_START + end = start + interface_info_bulk_width + sfp_interface_bulk_data = sfpi_obj.parse_sfp_info_bulk( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_VENDOR_NAME_OFFSET - XCVR_INTERFACE_DATA_START + end = start + XCVR_VENDOR_NAME_WIDTH + sfp_vendor_name_data = sfpi_obj.parse_vendor_name( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_VENDOR_PN_OFFSET - XCVR_INTERFACE_DATA_START + end = start + XCVR_VENDOR_PN_WIDTH + sfp_vendor_pn_data = sfpi_obj.parse_vendor_pn( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_HW_REV_OFFSET - XCVR_INTERFACE_DATA_START + end = start + vendor_rev_width + sfp_vendor_rev_data = sfpi_obj.parse_vendor_rev( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_VENDOR_SN_OFFSET - XCVR_INTERFACE_DATA_START + end = start + XCVR_VENDOR_SN_WIDTH + sfp_vendor_sn_data = sfpi_obj.parse_vendor_sn( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_VENDOR_OUI_OFFSET - XCVR_INTERFACE_DATA_START + end = start + XCVR_VENDOR_OUI_WIDTH + sfp_vendor_oui_data = sfpi_obj.parse_vendor_oui( + sfp_interface_bulk_raw[start: end], 0) + + start = XCVR_VENDOR_DATE_OFFSET - XCVR_INTERFACE_DATA_START + end = start + XCVR_VENDOR_DATE_WIDTH + sfp_vendor_date_data = sfpi_obj.parse_vendor_date( + sfp_interface_bulk_raw[start: end], 0) + transceiver_info_dict['type'] = sfp_interface_bulk_data['data']['type']['value'] + transceiver_info_dict['manufacturename'] = sfp_vendor_name_data['data']['Vendor Name']['value'] + transceiver_info_dict['modelname'] = sfp_vendor_pn_data['data']['Vendor PN']['value'] + transceiver_info_dict['hardwarerev'] = sfp_vendor_rev_data['data']['Vendor Rev']['value'] + transceiver_info_dict['serialnum'] = sfp_vendor_sn_data['data']['Vendor SN']['value'] + transceiver_info_dict['vendor_oui'] = sfp_vendor_oui_data['data']['Vendor OUI']['value'] + transceiver_info_dict['vendor_date'] = sfp_vendor_date_data[ + 'data']['VendorDataCode(YYYY-MM-DD Lot)']['value'] + transceiver_info_dict['Connector'] = sfp_interface_bulk_data['data']['Connector']['value'] + transceiver_info_dict['encoding'] = sfp_interface_bulk_data['data']['EncodingCodes']['value'] + transceiver_info_dict['ext_identifier'] = sfp_interface_bulk_data['data']['Extended Identifier']['value'] + transceiver_info_dict['ext_rateselect_compliance'] = sfp_interface_bulk_data['data']['RateIdentifier']['value'] + if self.sfp_type == QSFP_TYPE: + for key in qsfp_cable_length_tup: + if key in sfp_interface_bulk_data['data']: + transceiver_info_dict['cable_type'] = key + transceiver_info_dict['cable_length'] = str( + sfp_interface_bulk_data['data'][key]['value']) + + for key in qsfp_compliance_code_tup: + if key in sfp_interface_bulk_data['data']['Specification compliance']['value']: + compliance_code_dict[key] = sfp_interface_bulk_data['data']['Specification compliance']['value'][key]['value'] + transceiver_info_dict['specification_compliance'] = str( + compliance_code_dict) + + transceiver_info_dict['nominal_bit_rate'] = str( + sfp_interface_bulk_data['data']['Nominal Bit Rate(100Mbs)']['value']) + else: + for key in sfp_cable_length_tup: + if key in sfp_interface_bulk_data['data']: + transceiver_info_dict['cable_type'] = key + transceiver_info_dict['cable_length'] = str( + sfp_interface_bulk_data['data'][key]['value']) + + for key in sfp_compliance_code_tup: + if key in sfp_interface_bulk_data['data']['Specification compliance']['value']: + compliance_code_dict[key] = sfp_interface_bulk_data['data']['Specification compliance']['value'][key]['value'] + transceiver_info_dict['specification_compliance'] = str( + compliance_code_dict) + + transceiver_info_dict['nominal_bit_rate'] = str( + sfp_interface_bulk_data['data']['NominalSignallingRate(UnitsOf100Mbd)']['value']) + + return transceiver_info_dict + + def get_transceiver_bulk_status(self): + """ + Retrieves transceiver bulk status of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + rx_los |BOOLEAN |RX loss-of-signal status, True if has RX los, False if not. + tx_fault |BOOLEAN |TX fault status, True if has TX fault, False if not. + reset_status |BOOLEAN |reset status, True if SFP in reset, False if not. + lp_mode |BOOLEAN |low power mode status, True in lp mode, False if not. + tx_disable |BOOLEAN |TX disable status, True TX disabled, False if not. + tx_disabled_channel |HEX |disabled TX channels in hex, bits 0 to 3 represent channel 0 + | |to channel 3. + temperature |INT |module temperature in Celsius + voltage |INT |supply voltage in mV + txbias |INT |TX Bias Current in mA, n is the channel number, + | |for example, tx2bias stands for tx bias of channel 2. + rxpower |INT |received optical power in mW, n is the channel number, + | |for example, rx2power stands for rx power of channel 2. + txpower |INT |TX output power in mW, n is the channel number, + | |for example, tx2power stands for tx power of channel 2. + ======================================================================== + """ + transceiver_dom_info_dict = dict.fromkeys(self.dom_dict_keys, 'N/A') + + if self.sfp_type == OSFP_TYPE: + pass + + elif self.sfp_type == QSFP_TYPE: + if not self.dom_supported: + return transceiver_dom_info_dict + + offset = 0 + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return transceiver_dom_info_dict + + dom_data_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_DOM_BULK_DATA_START), QSFP_DOM_BULK_DATA_SIZE) + if dom_data_raw is None: + return transceiver_dom_info_dict + + if self.dom_temp_supported: + start = QSFP_TEMPE_OFFSET - QSFP_DOM_BULK_DATA_START + end = start + QSFP_TEMPE_WIDTH + dom_temperature_data = sfpd_obj.parse_temperature( + dom_data_raw[start: end], 0) + temp = self.__convert_string_to_num( + dom_temperature_data['data']['Temperature']['value']) + if temp is not None: + transceiver_dom_info_dict['temperature'] = temp + + if self.dom_volt_supported: + start = QSFP_VOLT_OFFSET - QSFP_DOM_BULK_DATA_START + end = start + QSFP_VOLT_WIDTH + dom_voltage_data = sfpd_obj.parse_voltage( + dom_data_raw[start: end], 0) + volt = self.__convert_string_to_num( + dom_voltage_data['data']['Vcc']['value']) + if volt is not None: + transceiver_dom_info_dict['voltage'] = volt + + start = QSFP_CHANNL_MON_OFFSET - QSFP_DOM_BULK_DATA_START + end = start + QSFP_CHANNL_MON_WITH_TX_POWER_WIDTH + dom_channel_monitor_data = sfpd_obj.parse_channel_monitor_params_with_tx_power( + dom_data_raw[start: end], 0) + + if self.dom_tx_power_supported: + transceiver_dom_info_dict['tx1power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX1Power']['value']) + transceiver_dom_info_dict['tx2power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX2Power']['value']) + transceiver_dom_info_dict['tx3power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX3Power']['value']) + transceiver_dom_info_dict['tx4power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX4Power']['value']) + + if self.dom_rx_power_supported: + transceiver_dom_info_dict['rx1power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['RX1Power']['value']) + transceiver_dom_info_dict['rx2power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['RX2Power']['value']) + transceiver_dom_info_dict['rx3power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['RX3Power']['value']) + transceiver_dom_info_dict['rx4power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['RX4Power']['value']) + + transceiver_dom_info_dict['tx1bias'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX1Bias']['value']) + transceiver_dom_info_dict['tx2bias'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX2Bias']['value']) + transceiver_dom_info_dict['tx3bias'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX3Bias']['value']) + transceiver_dom_info_dict['tx4bias'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TX4Bias']['value']) + + else: + if not self.dom_supported: + return transceiver_dom_info_dict + + offset = 256 + sfpd_obj = sff8472Dom() + if sfpd_obj is None: + return transceiver_dom_info_dict + sfpd_obj._calibration_type = self.calibration + + dom_data_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_DOM_BULK_DATA_START), SFP_DOM_BULK_DATA_SIZE) + + start = SFP_TEMPE_OFFSET - SFP_DOM_BULK_DATA_START + end = start + SFP_TEMPE_WIDTH + dom_temperature_data = sfpd_obj.parse_temperature( + dom_data_raw[start: end], 0) + + start = SFP_VOLT_OFFSET - SFP_DOM_BULK_DATA_START + end = start + SFP_VOLT_WIDTH + dom_voltage_data = sfpd_obj.parse_voltage( + dom_data_raw[start: end], 0) + + start = SFP_CHANNL_MON_OFFSET - SFP_DOM_BULK_DATA_START + end = start + SFP_CHANNL_MON_WIDTH + dom_channel_monitor_data = sfpd_obj.parse_channel_monitor_params( + dom_data_raw[start: end], 0) + + transceiver_dom_info_dict['temperature'] = self.__convert_string_to_num( + dom_temperature_data['data']['Temperature']['value']) + transceiver_dom_info_dict['voltage'] = self.__convert_string_to_num( + dom_voltage_data['data']['Vcc']['value']) + transceiver_dom_info_dict['rx1power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['RXPower']['value']) + transceiver_dom_info_dict['tx1bias'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TXBias']['value']) + transceiver_dom_info_dict['tx1power'] = self.__convert_string_to_num( + dom_channel_monitor_data['data']['TXPower']['value']) + + transceiver_dom_info_dict['rx_los'] = self.get_rx_los() + transceiver_dom_info_dict['tx_fault'] = self.get_tx_fault() + transceiver_dom_info_dict['reset_status'] = self.get_reset_status() + transceiver_dom_info_dict['lp_mode'] = self.get_lpmode() + + return transceiver_dom_info_dict + + def get_transceiver_threshold_info(self): + """ + Retrieves transceiver threshold info of this SFP + Returns: + A dict which contains following keys/values : + ======================================================================== + keys |Value Format |Information + ---------------------------|---------------|---------------------------- + temphighalarm |FLOAT |High Alarm Threshold value of temperature in Celsius. + templowalarm |FLOAT |Low Alarm Threshold value of temperature in Celsius. + temphighwarning |FLOAT |High Warning Threshold value of temperature in Celsius. + templowwarning |FLOAT |Low Warning Threshold value of temperature in Celsius. + vcchighalarm |FLOAT |High Alarm Threshold value of supply voltage in mV. + vcclowalarm |FLOAT |Low Alarm Threshold value of supply voltage in mV. + vcchighwarning |FLOAT |High Warning Threshold value of supply voltage in mV. + vcclowwarning |FLOAT |Low Warning Threshold value of supply voltage in mV. + rxpowerhighalarm |FLOAT |High Alarm Threshold value of received power in dBm. + rxpowerlowalarm |FLOAT |Low Alarm Threshold value of received power in dBm. + rxpowerhighwarning |FLOAT |High Warning Threshold value of received power in dBm. + rxpowerlowwarning |FLOAT |Low Warning Threshold value of received power in dBm. + txpowerhighalarm |FLOAT |High Alarm Threshold value of transmit power in dBm. + txpowerlowalarm |FLOAT |Low Alarm Threshold value of transmit power in dBm. + txpowerhighwarning |FLOAT |High Warning Threshold value of transmit power in dBm. + txpowerlowwarning |FLOAT |Low Warning Threshold value of transmit power in dBm. + txbiashighalarm |FLOAT |High Alarm Threshold value of tx Bias Current in mA. + txbiaslowalarm |FLOAT |Low Alarm Threshold value of tx Bias Current in mA. + txbiashighwarning |FLOAT |High Warning Threshold value of tx Bias Current in mA. + txbiaslowwarning |FLOAT |Low Warning Threshold value of tx Bias Current in mA. + ======================================================================== + """ + transceiver_dom_threshold_info_dict = dict.fromkeys( + self.threshold_dict_keys, 'N/A') + + if self.sfp_type == OSFP_TYPE: + pass + + elif self.sfp_type == QSFP_TYPE: + if not self.dom_supported or not self.qsfp_page3_available: + return transceiver_dom_threshold_info_dict + + # Dom Threshold data starts from offset 384 + # Revert offset back to 0 once data is retrieved + offset = QSFP_MODULE_UPPER_PAGE3_START + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return transceiver_dom_threshold_info_dict + + dom_module_threshold_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_MODULE_THRESHOLD_OFFSET), QSFP_MODULE_THRESHOLD_WIDTH) + if dom_module_threshold_raw is None: + return transceiver_dom_threshold_info_dict + + dom_module_threshold_data = sfpd_obj.parse_module_threshold_values( + dom_module_threshold_raw, 0) + + dom_channel_threshold_raw = self.__read_eeprom_specific_bytes((offset + QSFP_CHANNL_THRESHOLD_OFFSET), + QSFP_CHANNL_THRESHOLD_WIDTH) + if dom_channel_threshold_raw is None: + return transceiver_dom_threshold_info_dict + dom_channel_threshold_data = sfpd_obj.parse_channel_threshold_values( + dom_channel_threshold_raw, 0) + + # Threshold Data + transceiver_dom_threshold_info_dict['temphighalarm'] = dom_module_threshold_data['data']['TempHighAlarm']['value'] + transceiver_dom_threshold_info_dict['temphighwarning'] = dom_module_threshold_data['data']['TempHighWarning']['value'] + transceiver_dom_threshold_info_dict['templowalarm'] = dom_module_threshold_data['data']['TempLowAlarm']['value'] + transceiver_dom_threshold_info_dict['templowwarning'] = dom_module_threshold_data['data']['TempLowWarning']['value'] + transceiver_dom_threshold_info_dict['vcchighalarm'] = dom_module_threshold_data['data']['VccHighAlarm']['value'] + transceiver_dom_threshold_info_dict['vcchighwarning'] = dom_module_threshold_data['data']['VccHighWarning']['value'] + transceiver_dom_threshold_info_dict['vcclowalarm'] = dom_module_threshold_data['data']['VccLowAlarm']['value'] + transceiver_dom_threshold_info_dict['vcclowwarning'] = dom_module_threshold_data['data']['VccLowWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighalarm'] = dom_channel_threshold_data['data']['RxPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighwarning'] = dom_channel_threshold_data['data']['RxPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowalarm'] = dom_channel_threshold_data['data']['RxPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowwarning'] = dom_channel_threshold_data['data']['RxPowerLowWarning']['value'] + transceiver_dom_threshold_info_dict['txbiashighalarm'] = dom_channel_threshold_data['data']['TxBiasHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiashighwarning'] = dom_channel_threshold_data['data']['TxBiasHighWarning']['value'] + transceiver_dom_threshold_info_dict['txbiaslowalarm'] = dom_channel_threshold_data['data']['TxBiasLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiaslowwarning'] = dom_channel_threshold_data['data']['TxBiasLowWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerhighalarm'] = dom_channel_threshold_data['data']['TxPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerhighwarning'] = dom_channel_threshold_data['data']['TxPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerlowalarm'] = dom_channel_threshold_data['data']['TxPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerlowwarning'] = dom_channel_threshold_data['data']['TxPowerLowWarning']['value'] + + else: + offset = SFP_MODULE_ADDRA2_OFFSET + + if not self.dom_supported: + return transceiver_dom_threshold_info_dict + + sfpd_obj = sff8472Dom(None, self.calibration) + if sfpd_obj is None: + return transceiver_dom_threshold_info_dict + + dom_module_threshold_raw = self.__read_eeprom_specific_bytes((offset + SFP_MODULE_THRESHOLD_OFFSET), + SFP_MODULE_THRESHOLD_WIDTH) + if dom_module_threshold_raw is not None: + dom_module_threshold_data = sfpd_obj.parse_alarm_warning_threshold( + dom_module_threshold_raw, 0) + else: + return transceiver_dom_threshold_info_dict + + # Threshold Data + transceiver_dom_threshold_info_dict['temphighalarm'] = dom_module_threshold_data['data']['TempHighAlarm']['value'] + transceiver_dom_threshold_info_dict['templowalarm'] = dom_module_threshold_data['data']['TempLowAlarm']['value'] + transceiver_dom_threshold_info_dict['temphighwarning'] = dom_module_threshold_data['data']['TempHighWarning']['value'] + transceiver_dom_threshold_info_dict['templowwarning'] = dom_module_threshold_data['data']['TempLowWarning']['value'] + transceiver_dom_threshold_info_dict['vcchighalarm'] = dom_module_threshold_data['data']['VoltageHighAlarm']['value'] + transceiver_dom_threshold_info_dict['vcclowalarm'] = dom_module_threshold_data['data']['VoltageLowAlarm']['value'] + transceiver_dom_threshold_info_dict['vcchighwarning'] = dom_module_threshold_data[ + 'data']['VoltageHighWarning']['value'] + transceiver_dom_threshold_info_dict['vcclowwarning'] = dom_module_threshold_data['data']['VoltageLowWarning']['value'] + transceiver_dom_threshold_info_dict['txbiashighalarm'] = dom_module_threshold_data['data']['BiasHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiaslowalarm'] = dom_module_threshold_data['data']['BiasLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txbiashighwarning'] = dom_module_threshold_data['data']['BiasHighWarning']['value'] + transceiver_dom_threshold_info_dict['txbiaslowwarning'] = dom_module_threshold_data['data']['BiasLowWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerhighalarm'] = dom_module_threshold_data['data']['TXPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerlowalarm'] = dom_module_threshold_data['data']['TXPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['txpowerhighwarning'] = dom_module_threshold_data['data']['TXPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['txpowerlowwarning'] = dom_module_threshold_data['data']['TXPowerLowWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighalarm'] = dom_module_threshold_data['data']['RXPowerHighAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowalarm'] = dom_module_threshold_data['data']['RXPowerLowAlarm']['value'] + transceiver_dom_threshold_info_dict['rxpowerhighwarning'] = dom_module_threshold_data['data']['RXPowerHighWarning']['value'] + transceiver_dom_threshold_info_dict['rxpowerlowwarning'] = dom_module_threshold_data['data']['RXPowerLowWarning']['value'] + + for key in transceiver_dom_threshold_info_dict: + transceiver_dom_threshold_info_dict[key] = self.__convert_string_to_num( + transceiver_dom_threshold_info_dict[key]) + + return transceiver_dom_threshold_info_dict + + def get_reset_status(self): + """ + Retrieves the reset status of SFP + Returns: + A Boolean, True if reset enabled, False if disabled + """ + reg_status = self._api_helper.read_one_line_file( + "/".join([PORT_INFO_PATH, self.port_name, "qsfp_reset"])) + + return (reg_status == 0) + + def get_rx_los(self): + """ + Retrieves the RX LOS (lost-of-signal) status of SFP + Returns: + A Boolean, True if SFP has RX LOS, False if not. + Note : RX LOS status is latched until a call to get_rx_los or a reset. + """ + rx_los = False + if self.sfp_type == OSFP_TYPE: + return False + + elif self.sfp_type == QSFP_TYPE: + offset = 0 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_RX_LOS_STATUS_OFFSET), QSFP_CHANNL_RX_LOS_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + rx_los_data = int(dom_channel_monitor_raw[0], 16) + rx1_los = (rx_los_data & 0x01 != 0) + rx2_los = (rx_los_data & 0x02 != 0) + rx3_los = (rx_los_data & 0x04 != 0) + rx4_los = (rx_los_data & 0x08 != 0) + rx_los = (rx1_los and rx2_los and rx3_los and rx4_los) + else: + offset = 256 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_CHANNL_STATUS_OFFSET), SFP_CHANNL_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + rx_los_data = int(dom_channel_monitor_raw[0], 16) + rx_los = (rx_los_data & 0x02 != 0) + + return rx_los + + def get_tx_fault(self): + """ + Retrieves the TX fault status of SFP + Returns: + A Boolean, True if SFP has TX fault, False if not + Note : TX fault status is lached until a call to get_tx_fault or a reset. + """ + tx4_fault = False + + if self.sfp_type == OSFP_TYPE or not self.dom_supported: + return False + + elif self.sfp_type == QSFP_TYPE: + offset = 0 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_TX_FAULT_STATUS_OFFSET), QSFP_CHANNL_TX_FAULT_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_fault_data = int(dom_channel_monitor_raw[0], 16) + tx1_fault = (tx_fault_data & 0x01 != 0) + tx2_fault = (tx_fault_data & 0x02 != 0) + tx3_fault = (tx_fault_data & 0x04 != 0) + tx4_fault = (tx_fault_data & 0x08 != 0) + tx4_fault = ( + tx1_fault and tx2_fault and tx3_fault and tx4_fault) + else: + offset = 256 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_CHANNL_STATUS_OFFSET), SFP_CHANNL_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_fault_data = int(dom_channel_monitor_raw[0], 16) + tx4_fault = (tx_fault_data & 0x04 != 0) + + return tx4_fault + + def get_tx_disable(self): + """ + Retrieves the tx_disable status of this SFP + Returns: + A Boolean, True if tx_disable is enabled, False if disabled + """ + + tx_disable = False + + if self.sfp_type == OSFP_TYPE and not self.dom_supported: + return False + + elif self.sfp_type == QSFP_TYPE: + offset = 0 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_DISABLE_STATUS_OFFSET), QSFP_CHANNL_DISABLE_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_disable_data = int(dom_channel_monitor_raw[0], 16) + tx1_disable = (tx_disable_data & 0x01 != 0) + tx2_disable = (tx_disable_data & 0x02 != 0) + tx3_disable = (tx_disable_data & 0x04 != 0) + tx4_disable = (tx_disable_data & 0x08 != 0) + tx_disable = ( + tx1_disable and tx2_disable and tx3_disable and tx4_disable) + else: + offset = 256 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_CHANNL_STATUS_OFFSET), SFP_CHANNL_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_disable_data = int(dom_channel_monitor_raw[0], 16) + tx_disable = (tx_disable_data & 0xC0 != 0) + + return tx_disable + + def get_tx_disable_channel(self): + """ + Retrieves the TX disabled channels in this SFP + Returns: + A hex of 4 bits (bit 0 to bit 3 as channel 0 to channel 3) to represent + TX channels which have been disabled in this SFP. + As an example, a returned value of 0x5 indicates that channel 0 + and channel 2 have been disabled. + """ + tx_disable_list = [False, False, False, False] + + if self.sfp_type == OSFP_TYPE: + pass + + elif self.sfp_type == QSFP_TYPE: + offset = 0 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + QSFP_CHANNL_DISABLE_STATUS_OFFSET), QSFP_CHANNL_DISABLE_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_disable_data = int(dom_channel_monitor_raw[0], 16) + tx_disable_list[0] = (tx_disable_data & 0x01 != 0) + tx_disable_list[1] = (tx_disable_data & 0x02 != 0) + tx_disable_list[2] = (tx_disable_data & 0x04 != 0) + tx_disable_list[3] = (tx_disable_data & 0x08 != 0) + else: + offset = 256 + dom_channel_monitor_raw = self.__read_eeprom_specific_bytes( + (offset + SFP_CHANNL_STATUS_OFFSET), SFP_CHANNL_STATUS_WIDTH) + if dom_channel_monitor_raw is not None: + tx_disable_data = int(dom_channel_monitor_raw[0], 16) + tx_disable_list[0] = (tx_disable_data & 0xC0 != 0) + + tx_disabled = 0 + for i in range(len(tx_disable_list)): + if tx_disable_list[i]: + tx_disabled |= 1 << i + return tx_disabled + + def get_lpmode(self): + """ + Retrieves the lpmode (low power mode) status of this SFP + Returns: + A Boolean, True if lpmode is enabled, False if disabled + """ + # Not support + return False + + def get_power_override(self): + """ + Retrieves the power-override status of this SFP + Returns: + A Boolean, True if power-override is enabled, False if disabled + """ + if self.sfp_type == QSFP_TYPE: + offset = 0 + sfpd_obj = sff8436Dom() + if sfpd_obj is None: + return False + + dom_control_raw = self.__read_eeprom_specific_bytes( + QSFP_CONTROL_OFFSET, QSFP_CONTROL_WIDTH) if self.get_presence() else None + if dom_control_raw is not None: + dom_control_data = sfpd_obj.parse_control_bytes( + dom_control_raw, 0) + power_override = ( + 'On' == dom_control_data['data']['PowerOverride']['value']) + return power_override + else: + return False + + def get_temperature(self): + """ + Retrieves the temperature of this SFP + Returns: + An integer number of current temperature in Celsius + """ + transceiver_bulk_status = self.get_transceiver_bulk_status() + return transceiver_bulk_status.get("temperature", "N/A") + + def get_voltage(self): + """ + Retrieves the supply voltage of this SFP + Returns: + An integer number of supply voltage in mV + """ + transceiver_bulk_status = self.get_transceiver_bulk_status() + return transceiver_bulk_status.get("voltage", "N/A") + + def get_tx_bias(self): + """ + Retrieves the TX bias current of this SFP + Returns: + A list of four integer numbers, representing TX bias in mA + for channel 0 to channel 4. + Ex. ['110.09', '111.12', '108.21', '112.09'] + """ + transceiver_bulk_status = self.get_transceiver_bulk_status() + tx1_bs = transceiver_bulk_status.get("tx1bias", "N/A") + tx2_bs = transceiver_bulk_status.get("tx2bias", "N/A") + tx3_bs = transceiver_bulk_status.get("tx3bias", "N/A") + tx4_bs = transceiver_bulk_status.get("tx4bias", "N/A") + tx_bias_list = [tx1_bs, tx2_bs, tx3_bs, tx4_bs] + return tx_bias_list + + def get_rx_power(self): + """ + Retrieves the received optical power for this SFP + Returns: + A list of four integer numbers, representing received optical + power in mW for channel 0 to channel 4. + Ex. ['1.77', '1.71', '1.68', '1.70'] + """ + rx_power_list = [] + transceiver_bulk_status = self.get_transceiver_bulk_status() + rx1_p = transceiver_bulk_status.get("rx1power", "N/A") + rx2_p = transceiver_bulk_status.get("rx2power", "N/A") + rx3_p = transceiver_bulk_status.get("rx3power", "N/A") + rx4_p = transceiver_bulk_status.get("rx4power", "N/A") + rx_power_list = [rx1_p, rx2_p, rx3_p, rx4_p] + return rx_power_list + + def get_tx_power(self): + """ + Retrieves the TX power of this SFP + Returns: + A list of four integer numbers, representing TX power in mW + for channel 0 to channel 4. + Ex. ['1.86', '1.86', '1.86', '1.86'] + """ + tx_power_list = [] + transceiver_bulk_status = self.get_transceiver_bulk_status() + tx1_p = transceiver_bulk_status.get("tx1power", "N/A") + tx2_p = transceiver_bulk_status.get("tx2power", "N/A") + tx3_p = transceiver_bulk_status.get("tx3power", "N/A") + tx4_p = transceiver_bulk_status.get("tx4power", "N/A") + tx_power_list = [tx1_p, tx2_p, tx3_p, tx4_p] + return tx_power_list + + def reset(self): + """ + Reset SFP and return all user module settings to their default srate. + Returns: + A boolean, True if successful, False if not + """ + if self.sfp_type != QSFP_TYPE: + return False + + try: + reg_file = open( + "/".join([PORT_INFO_PATH, self.port_name, "qsfp_reset"]), "w") + except IOError as e: + #print("Error: unable to open file: %s" % str(e)) + return False + + # Convert our register value back to a hex string and write back + reg_file.seek(0) + reg_file.write(hex(0)) + reg_file.close() + + # Sleep 1 second to allow it to settle + time.sleep(1) + + # Flip the bit back high and write back to the register to take port out of reset + try: + reg_file = open( + "/".join([PORT_INFO_PATH, self.port_name, "qsfp_reset"]), "w") + except IOError as e: + #print("Error: unable to open file: %s" % str(e)) + return False + + reg_file.seek(0) + reg_file.write(hex(1)) + reg_file.close() + + return True + + def tx_disable(self, tx_disable): + """ + Disable SFP TX for all channels + Args: + tx_disable : A Boolean, True to enable tx_disable mode, False to disable + tx_disable mode. + Returns: + A boolean, True if tx_disable is set successfully, False if not + """ + if self.sfp_type == QSFP_TYPE: + sysfsfile_eeprom = None + try: + tx_disable_ctl = 0xf if tx_disable else 0x0 + buffer = create_string_buffer(1) + buffer[0] = chr(tx_disable_ctl) + # Write to eeprom + sysfsfile_eeprom = open( + self.port_to_eeprom_mapping[self.port_num], "r+b") + sysfsfile_eeprom.seek(QSFP_CONTROL_OFFSET) + sysfsfile_eeprom.write(buffer[0]) + except IOError as e: + #print("Error: unable to open file: %s" % str(e)) + return False + finally: + if sysfsfile_eeprom is not None: + sysfsfile_eeprom.close() + time.sleep(0.01) + return True + return False + + def tx_disable_channel(self, channel, disable): + """ + Sets the tx_disable for specified SFP channels + Args: + channel : A hex of 4 bits (bit 0 to bit 3) which represent channel 0 to 3, + e.g. 0x5 for channel 0 and channel 2. + disable : A boolean, True to disable TX channels specified in channel, + False to enable + Returns: + A boolean, True if successful, False if not + """ + if self.sfp_type == QSFP_TYPE: + sysfsfile_eeprom = None + try: + channel_state = self.get_tx_disable_channel() + tx_enable_mask = [0xe, 0xd, 0xb, 0x7] + tx_disable_mask = [0x1, 0x3, 0x7, 0xf] + tx_disable_ctl = channel_state | tx_disable_mask[ + channel] if disable else channel_state & tx_enable_mask[channel] + buffer = create_string_buffer(1) + buffer[0] = chr(tx_disable_ctl) + # Write to eeprom + sysfsfile_eeprom = open( + self.port_to_eeprom_mapping[self.port_num], "r+b") + sysfsfile_eeprom.seek(QSFP_CONTROL_OFFSET) + sysfsfile_eeprom.write(buffer[0]) + except IOError as e: + #print("Error: unable to open file: %s" % str(e)) + return False + finally: + if sysfsfile_eeprom is not None: + sysfsfile_eeprom.close() + time.sleep(0.01) + return True + return False + + def set_lpmode(self, lpmode): + """ + Sets the lpmode (low power mode) of SFP + Args: + lpmode: A Boolean, True to enable lpmode, False to disable it + Note : lpmode can be overridden by set_power_override + Returns: + A boolean, True if lpmode is set successfully, False if not + """ + # Not support + return False + + def set_power_override(self, power_override, power_set): + """ + Sets SFP power level using power_override and power_set + Args: + power_override : + A Boolean, True to override set_lpmode and use power_set + to control SFP power, False to disable SFP power control + through power_override/power_set and use set_lpmode + to control SFP power. + power_set : + Only valid when power_override is True. + A Boolean, True to set SFP to low power mode, False to set + SFP to high power mode. + Returns: + A boolean, True if power-override and power_set are set successfully, + False if not + """ + if self.sfp_type == QSFP_TYPE: + try: + power_override_bit = 0 + if power_override: + power_override_bit |= 1 << 0 + + power_set_bit = 0 + if power_set: + power_set_bit |= 1 << 1 + + buffer = create_string_buffer(1) + buffer[0] = chr(power_override_bit | power_set_bit) + # Write to eeprom + sysfsfile_eeprom = open( + self.port_to_eeprom_mapping[self.port_num], "r+b") + sysfsfile_eeprom.seek(QSFP_POWEROVERRIDE_OFFSET) + sysfsfile_eeprom.write(buffer[0]) + except IOError as e: + #print("Error: unable to open file: %s" % str(e)) + return False + finally: + if sysfsfile_eeprom is not None: + sysfsfile_eeprom.close() + time.sleep(0.01) + return True + return False + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + def get_name(self): + """ + Retrieves the name of the device + Returns: + string: The name of the device + """ + sfputil_helper = SfpUtilHelper() + sfputil_helper.read_porttab_mappings( + self.__get_path_to_port_config_file()) + name = sfputil_helper.logical[self.index] or "Unknown" + return name + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + # Get path for access port presence status + sysfs_filename = "sfp_modabs" if self.sfp_type == SFP_TYPE else "qsfp_modprs" + reg_path = "/".join([PORT_INFO_PATH, self.port_name, sysfs_filename]) + + content = self._api_helper.read_one_line_file(reg_path) + reg_value = int(content) + + return (reg_value == 0) + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + transceiver_dom_info_dict = self.get_transceiver_info() + return transceiver_dom_info_dict.get("modelname", "N/A") + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + transceiver_dom_info_dict = self.get_transceiver_info() + return transceiver_dom_info_dict.get("serialnum", "N/A") + + 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 not self.get_reset_status() diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/thermal.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/thermal.py new file mode 100644 index 000000000000..ed1dad64810d --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/thermal.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Thermal contains an implementation of SONiC Platform Base API and +# provides the thermal device status which are available in the platform +# +############################################################################# + +import json +import math +import os.path + +try: + from sonic_platform_base.thermal_base import ThermalBase + from helper import APIHelper +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +SENSORS_HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/{0}-00{1}" +SENSORS_MUX_HWMON_PATH = "/sys/bus/i2c/devices/i2c-{0}/i2c-{1}/{1}-00{2}" + +DEFAULT_VAL = 0.0 + +class Thermal(ThermalBase): + """Platform-specific Thermal class""" + + def __init__(self, thermal_index): + ThermalBase.__init__(self) + self.thermal_index = thermal_index + self._api_helper = APIHelper() + + ###### Thermal list defined ####### + # (NAME , Hwmon name , I2CBUS, I2C_MUX, I2C Address, T/F I2C_MUX, Temp_NUM) + self.THERMAL_LIST = [ + ('TEMP_lm75b_i2c_9_48', 'lm75b', 66, 9, '48', True, 1), + ('TEMP_lm75b_i2c_67_4d', 'lm75b', 67, 0, '4d', False, 1), + ('TEMP_dps1100_i2c_75_58_1', 'dps1100', 75, 0, '58', False, 1), + ('TEMP_dps1100_i2c_75_58_2', 'dps1100', 75, 0, '58', False, 2), + ('TEMP_dps1100_i2c_76_59_1', 'dps1100', 76, 0, '59', False, 1), + ('TEMP_dps1100_i2c_76_59_2', 'dps1100', 76, 0, '59', False, 2), + ('TEMP_syscpld_i2c_70_0d', 'syscpld', 70, 0, '0d', False, 1), + ('TEMP_jc42_i2c_1_18', 'jc42', 1, 0, '18', False, 1), + ('TEMP_core_tmp_1', 'coretemp', 0, 0, '00', False, 1), + ('TEMP_core_tmp_2', 'coretemp', 0, 0, '00', False, 2), + ('TEMP_core_tmp_3', 'coretemp', 0, 0, '00', False, 3), + ('TEMP_core_tmp_4', 'coretemp', 0, 0, '0', False, 4), + ] + + if self.THERMAL_LIST[self.thermal_index][1] == 'coretemp': + # Specifi check coretemp condition, Because Hwmon path is not standard. + self.hwmon_path = "/sys/bus/platform/drivers/coretemp/coretemp.0/" + else: + if self.THERMAL_LIST[self.thermal_index][5]: + self.hwmon_path = SENSORS_MUX_HWMON_PATH.format( + self.THERMAL_LIST[self.thermal_index][2], + self.THERMAL_LIST[self.thermal_index][3], + self.THERMAL_LIST[self.thermal_index][4]) + else: + self.hwmon_path = SENSORS_HWMON_PATH.format( + self.THERMAL_LIST[self.thermal_index][2], + self.THERMAL_LIST[self.thermal_index][4]) + self.ss_index = self.THERMAL_LIST[self.thermal_index][6] + + # Search directory + label = self.THERMAL_LIST[self.thermal_index][1] + self.hwmon_path = self.__search_dirpath_contain( + self.hwmon_path, label, "name") + + def __search_dirpath_contain(self, directory, search_str, file_start): + # Searching file_start from current directory inclunding sub-directory. + self.dirpath = [] + for dirpath, dirnames, files in os.walk(directory): + for name in files: + file_path = os.path.join(dirpath, name) + if name.startswith(file_start) and search_str in self._api_helper.read_txt_file(file_path): + self.dirpath.append(dirpath) + return self.dirpath + + def __get_temp(self, temp_file): + for hwmon_path in self.dirpath: + try: + temp_file_path = os.path.join(hwmon_path, temp_file) + raw_temp = self._api_helper.read_txt_file(temp_file_path) + temp = float(raw_temp)/1000 + return "{:.3f}".format(temp) + except: + continue + return DEFAULT_VAL + + def __set_threshold(self, file_name, temperature): + for hwmon_path in self.dirpath: + temp_file_path = os.path.join(hwmon_path, file_name) + try: + with open(temp_file_path, 'w') as fd: + fd.write(str(temperature)) + return True + except IOError: + continue + return False + + 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 + """ + temp_file = "temp{}_input".format(self.ss_index) + return self.__get_temp(temp_file) + + 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 + """ + temp_file = "temp{}_max".format(self.ss_index) + return self.__get_temp(temp_file) + + 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 + """ + + temp_file = "temp{}_max".format(self.ss_index) + is_set = self.__set_threshold(temp_file, int(temperature*1000)) + return is_set + + def get_low_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 + """ + temp_min_file = [] + temp_min_file.append("temp{}_min".format(self.ss_index)) + temp_min_file.append("temp{}_max_hyst".format(self.ss_index)) + + for temp_file in temp_min_file: + result = self.__get_temp(temp_file) + if result is not False: + break + return result + + def set_low_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 + """ + temp_min_file = [] + temp_min_file.append("temp{}_min".format(self.ss_index)) + temp_min_file.append("temp{}_max_hyst".format(self.ss_index)) + + for temp_file in temp_min_file: + result = self.__set_threshold(temp_file, int(temperature*1000)) + if result is not False: + break + return result + + ############################################################## + ###################### Device methods ######################## + ############################################################## + + def get_name(self): + """ + Retrieves the name of the thermal device + Returns: + string: The name of the thermal device + """ + return self.THERMAL_LIST[self.thermal_index][0] + + def get_presence(self): + """ + Retrieves the presence of the PSU + Returns: + bool: True if PSU is present, False if not + """ + for hwmon_path in self.dirpath: + try: + temp_file = "temp{}_input".format(self.ss_index) + temp_file_path = os.path.join(hwmon_path, temp_file) + if os.path.isfile(temp_file_path): + return True + except: + continue + return False + + def get_model(self): + """ + Retrieves the model number (or part number) of the device + Returns: + string: Model/part number of device + """ + return self.THERMAL_LIST[self.thermal_index][1] + + def get_serial(self): + """ + Retrieves the serial number of the device + Returns: + string: Serial number of device + """ + return "N/A" + + def get_status(self): + """ + Retrieves the operational status of the device + Returns: + A boolean value, True if device is operating properly, False if not + """ + if not self.get_presence(): + return False + + for hwmon_path in self.dirpath: + try: + fault_file = "temp{}_fault".format(self.ss_index) + fault_file_path = os.path.join(hwmon_path, fault_file) + if not os.path.isfile(fault_file_path): + return True + + raw_txt = self._api_helper.read_txt_file(fault_file_path) + return int(raw_txt) == 0 + except: + continue + return False diff --git a/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/watchdog.py b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/watchdog.py new file mode 100644 index 000000000000..b539bc06f618 --- /dev/null +++ b/device/celestica/x86_64-cel_questone2bd-r0/sonic_platform/watchdog.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + +############################################################################# +# Celestica +# +# Watchdog contains an implementation of SONiC Platform Base API +# +############################################################################# +import ctypes +import fcntl +import os +import subprocess +import time +import array + +try: + from sonic_platform_base.watchdog_base import WatchdogBase +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +""" ioctl constants """ +IO_WRITE = 0x40000000 +IO_READ = 0x80000000 +IO_READ_WRITE = 0xC0000000 +IO_SIZE_INT = 0x00040000 +IO_SIZE_40 = 0x00280000 +IO_TYPE_WATCHDOG = ord('W') << 8 + +WDR_INT = IO_READ | IO_SIZE_INT | IO_TYPE_WATCHDOG +WDR_40 = IO_READ | IO_SIZE_40 | IO_TYPE_WATCHDOG +WDWR_INT = IO_READ_WRITE | IO_SIZE_INT | IO_TYPE_WATCHDOG + +""" Watchdog ioctl commands """ +WDIOC_GETSUPPORT = 0 | WDR_40 +WDIOC_GETSTATUS = 1 | WDR_INT +WDIOC_GETBOOTSTATUS = 2 | WDR_INT +WDIOC_GETTEMP = 3 | WDR_INT +WDIOC_SETOPTIONS = 4 | WDR_INT +WDIOC_KEEPALIVE = 5 | WDR_INT +WDIOC_SETTIMEOUT = 6 | WDWR_INT +WDIOC_GETTIMEOUT = 7 | WDR_INT +WDIOC_SETPRETIMEOUT = 8 | WDWR_INT +WDIOC_GETPRETIMEOUT = 9 | WDR_INT +WDIOC_GETTIMELEFT = 10 | WDR_INT + +""" Watchdog status constants """ +WDIOS_DISABLECARD = 0x0001 +WDIOS_ENABLECARD = 0x0002 + +WDT_COMMON_ERROR = -1 +WD_MAIN_IDENTITY = "iTCO_wdt" +WDT_SYSFS_PATH = "/sys/class/watchdog/" + + +class Watchdog(WatchdogBase): + + def __init__(self): + + self.watchdog, self.wdt_main_dev_name = self._get_wdt() + self.status_path = "/sys/class/watchdog/%s/status" % self.wdt_main_dev_name + self.state_path = "/sys/class/watchdog/%s/state" % self.wdt_main_dev_name + self.timeout_path = "/sys/class/watchdog/%s/timeout" % self.wdt_main_dev_name + # Set default value + self._disable() + self.armed = False + self.timeout = self._gettimeout(self.timeout_path) + + def _is_wd_main(self, dev): + """ + Checks watchdog identity + """ + identity = self._read_file( + "{}/{}/identity".format(WDT_SYSFS_PATH, dev)) + return identity == WD_MAIN_IDENTITY + + def _get_wdt(self): + """ + Retrieves watchdog device + """ + wdt_main_dev_list = [dev for dev in os.listdir( + "/dev/") if dev.startswith("watchdog") and self._is_wd_main(dev)] + if not wdt_main_dev_list: + return None + wdt_main_dev_name = wdt_main_dev_list[0] + watchdog_device_path = "/dev/{}".format(wdt_main_dev_name) + watchdog = os.open(watchdog_device_path, os.O_RDWR) + return watchdog, wdt_main_dev_name + + def _read_file(self, file_path): + """ + Read text file + """ + try: + with open(file_path, "r") as fd: + txt = fd.read() + except IOError: + return WDT_COMMON_ERROR + return txt.strip() + + def _enable(self): + """ + Turn on the watchdog timer + """ + req = array.array('h', [WDIOS_ENABLECARD]) + fcntl.ioctl(self.watchdog, WDIOC_SETOPTIONS, req, False) + + def _disable(self): + """ + Turn off the watchdog timer + """ + req = array.array('h', [WDIOS_DISABLECARD]) + fcntl.ioctl(self.watchdog, WDIOC_SETOPTIONS, req, False) + + def _keepalive(self): + """ + Keep alive watchdog timer + """ + fcntl.ioctl(self.watchdog, WDIOC_KEEPALIVE) + + def _settimeout(self, seconds): + """ + Set watchdog timer timeout + @param seconds - timeout in seconds + @return is the actual set timeout + """ + req = array.array('I', [seconds]) + fcntl.ioctl(self.watchdog, WDIOC_SETTIMEOUT, req, True) + return int(req[0]) + + def _gettimeout(self, timeout_path): + """ + Get watchdog timeout + @return watchdog timeout + """ + req = array.array('I', [0]) + fcntl.ioctl(self.watchdog, WDIOC_GETTIMEOUT, req, True) + + return int(req[0]) + + def _gettimeleft(self): + """ + Get time left before watchdog timer expires + @return time left in seconds + """ + req = array.array('I', [0]) + fcntl.ioctl(self.watchdog, WDIOC_GETTIMELEFT, req, True) + + return int(req[0]) + + ################################################################# + + def arm(self, seconds): + """ + Arm the hardware watchdog with a timeout of seconds. + If the watchdog is currently armed, calling this function will + simply reset the timer to the provided value. If the underlying + hardware does not support the value provided in , this + method should arm the watchdog with the *next greater* available + value. + Returns: + An integer specifying the *actual* number of seconds the watchdog + was armed with. On failure returns -1. + """ + + ret = WDT_COMMON_ERROR + if seconds < 0: + return ret + + try: + if self.timeout != seconds: + self.timeout = self._settimeout(seconds) + if self.armed: + self._keepalive() + else: + self._enable() + self.armed = True + ret = self.timeout + except IOError as e: + pass + + return ret + + def disarm(self): + """ + Disarm the hardware watchdog + Returns: + A boolean, True if watchdog is disarmed successfully, False if not + """ + disarmed = False + if self.is_armed(): + try: + self._disable() + self.armed = False + disarmed = True + except IOError: + pass + + return disarmed + + def is_armed(self): + """ + Retrieves the armed state of the hardware watchdog. + Returns: + A boolean, True if watchdog is armed, False if not + """ + + return self.armed + + def get_remaining_time(self): + """ + If the watchdog is armed, retrieve the number of seconds remaining on + the watchdog timer + Returns: + An integer specifying the number of seconds remaining on thei + watchdog timer. If the watchdog is not armed, returns -1. + """ + + timeleft = WDT_COMMON_ERROR + + if self.armed: + try: + timeleft = self._gettimeleft() + except IOError: + pass + + return timeleft + + def __del__(self): + """ + Close watchdog + """ + + os.close(self.watchdog) diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.install b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.install index 81f61779928e..3876544e0e17 100644 --- a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.install +++ b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.install @@ -4,4 +4,6 @@ questone2bd/systemd/platform-modules-questone2bd.service lib/systemd/system tools/fanctrl/fanctrl.service lib/systemd/system questone2bd/scripts/get_fan_speed.sh usr/local/bin questone2bd/scripts/set_fan_speed.sh usr/local/bin -questone2bd/cfg/pid_config_questone2bd.ini usr/local/etc \ No newline at end of file +questone2bd/cfg/pid_config_questone2bd.ini usr/local/etc +questone2bd/modules/sonic_platform-1.0-py2-none-any.whl usr/share/sonic/device/x86_64-cel_questone2bd-r0 +services/platform_api/platform_api_mgnt.sh usr/local/bin diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.postinst b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.postinst index bf009203f2c5..e4125d207258 100644 --- a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.postinst +++ b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-questone2bd.postinst @@ -5,3 +5,5 @@ systemctl start platform-modules-questone2bd.service systemctl enable fanctrl.service systemctl start fanctrl.service + +/usr/local/bin/platform_api_mgnt.sh install diff --git a/platform/broadcom/sonic-platform-modules-cel/questone2bd/setup.py b/platform/broadcom/sonic-platform-modules-cel/questone2bd/setup.py new file mode 100644 index 000000000000..5d51370969db --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/questone2bd/setup.py @@ -0,0 +1,34 @@ +from setuptools import setup + +DEVICE_NAME = 'celestica' +HW_SKU = 'x86_64-cel_questone2bd-r0' + +setup( + name='sonic-platform', + version='1.0', + description='SONiC platform API implementation on Celestica Platforms', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-buildimage', + maintainer='Wirut Getbamrung', + maintainer_email='wgetbumr@celestica.com', + packages=[ + 'sonic_platform', + ], + package_dir={ + 'sonic_platform': '../../../../device/{}/{}/sonic_platform'.format(DEVICE_NAME, HW_SKU)}, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Topic :: Utilities', + ], + keywords='sonic SONiC platform PLATFORM', +)