[platform/device] - Implement Silverstone platform API [Chassis/Fan] (#3706)

Implement part of the Chassis and Fan related APIs.

- Chassis APIs

get_base_mac()
get_serial_number()
get_serial_number()
get_system_eeprom_info()
get_reboot_cause()

- Fan APIs

get_direction()
get_speed()
get_target_speed()
get_speed_tolerance()
set_speed()
set_status_led()
get_target_speed()

- Fan APIs base on Device API

get_name()
get_presence()
get_model()
get_serial()
get_status()

Signed-off-by: Wirut Getbamrung wgetbumr@celestica.com
This commit is contained in:
Wirut Getbamrung 2019-11-07 09:56:17 +07:00 committed by lguohan
parent 3b51cec9a3
commit 65fc916dcf
10 changed files with 626 additions and 2 deletions

View File

@ -0,0 +1,2 @@
__all__ = ["platform", "chassis"]
from sonic_platform import *

View File

@ -0,0 +1,114 @@
#!/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
#
#############################################################################
import sys
import re
import os
import subprocess
import json
try:
from sonic_platform_base.chassis_base import ChassisBase
from sonic_platform.eeprom import Tlv
from sonic_platform.fan import Fan
from helper import APIHelper
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
NUM_FAN_TRAY = 7
NUM_FAN = 2
NUM_PSU = 2
NUM_THERMAL = 5
NUM_SFP = 32
NUM_COMPONENT = 5
IPMI_OEM_NETFN = "0x3A"
IPMI_GET_REBOOT_CAUSE = "0x03 0x00 0x01 0x06"
class Chassis(ChassisBase):
"""Platform-specific Chassis class"""
def __init__(self):
self.config_data = {}
ChassisBase.__init__(self)
self._eeprom = Tlv()
self._api_helper = APIHelper()
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)
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.
"""
status, raw_cause = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, IPMI_GET_REBOOT_CAUSE)
hx_cause = raw_cause.split()[0] if status else 00
reboot_cause = {
"00": self.REBOOT_CAUSE_HARDWARE_OTHER,
"11": self.REBOOT_CAUSE_POWER_LOSS,
"22": self.REBOOT_CAUSE_NON_HARDWARE,
"33": self.REBOOT_CAUSE_HARDWARE_OTHER,
"44": self.REBOOT_CAUSE_NON_HARDWARE,
"55": self.REBOOT_CAUSE_NON_HARDWARE,
"66": self.REBOOT_CAUSE_WATCHDOG,
"77": self.REBOOT_CAUSE_NON_HARDWARE
}.get(hx_cause, self.REBOOT_CAUSE_HARDWARE_OTHER)
description = {
"00": "Unknown reason",
"11": "The last reset is Power on reset",
"22": "The last reset is soft-set CPU warm reset",
"33": "The last reset is soft-set CPU cold reset",
"44": "The last reset is CPU warm reset",
"55": "The last reset is CPU cold reset",
"66": "The last reset is watchdog reset",
"77": "The last reset is power cycle reset"
}.get(hx_cause, "Unknown reason")
return (reboot_cause, description)

View File

@ -0,0 +1,113 @@
#!/usr/bin/env python
#############################################################################
# Celestica Silverstone
#
# 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'
TLV_EEPROM_I2C_BUS = 0
TLV_EEPROM_I2C_ADDR = 56
class Tlv(eeprom_tlvinfo.TlvInfoDecoder):
EEPROM_DECODE_HEADLINES = 6
def __init__(self):
self._eeprom_path = "/sys/class/i2c-adapter/i2c-{0}/{0}-00{1}/eeprom".format(TLV_EEPROM_I2C_BUS, TLV_EEPROM_I2C_ADDR)
super(Tlv, 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_serial(self):
return self._eeprom.get('0x23', "Undefined.")
def get_mac(self):
return self._eeprom.get('0x24', "Undefined.")

View File

@ -0,0 +1,276 @@
#!/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-5F", "FAN-5R", "FAN-6F", "FAN-6R", "FAN-7F", "FAN-7R"]
IPMI_OEM_NETFN = "0x3A"
IPMI_SENSOR_NETFN = "0x04"
IPMI_FAN_SPEED_CMD = "0x2D {}"
IPMI_AIR_FLOW_CMD = "0x0A {}"
IPMI_FAN_PRESENT_CMD = "0x06 0x03 {}"
IPMI_SET_FAN_LED_CMD = "0x07 {} {}"
IPMI_GET_FAN_LED_CMD = "0x08 {}"
IPMI_SET_PWM = "0x03 0x01 0x02 {} {}"
IPMI_FRU_PRINT_ID = "ipmitool fru print {}"
IPMI_FRU_MODEL_KEY = "Board Part Number"
IPMI_FRU_SERIAL_KEY = "Board Serial"
MAX_OUTLET = 24700
MAX_INLET = 29700
SPEED_TOLERANCE = 10
FAN1_FRONT_SS_ID = "0x0D"
FAN1_REAR_SS_ID = "0x45"
FAN_LED_OFF_CMD = "0x00"
FAN_LED_GREEN_CMD = "0x01"
FAN_LED_RED_CMD = "0x02"
FAN1_LED_CMD = "0x04"
FAN_PWM_REGISTER_START = 0x22
FAN_PWM_REGISTER_STEP = 0x10
FAN1_FRU_ID = 6
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._api_helper = APIHelper()
self.index = self.fan_tray_index * 2 + self.fan_index
def get_direction(self):
"""
Retrieves the direction of fan
Returns:
A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST
depending on fan direction
"""
direction = self.FAN_DIRECTION_EXHAUST
status, raw_flow = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, IPMI_AIR_FLOW_CMD.format(hex(self.fan_tray_index)))
if status and raw_flow == "01":
direction = self.FAN_DIRECTION_INTAKE
return direction
def get_speed(self):
"""
Retrieves the speed of fan as a percentage of full speed
Returns:
An integer, the percentage of full fan speed, in the range 0 (off)
to 100 (full speed)
Note:
M = 150
Max F2B = 24700 RPM
Max B2F = 29700 RPM
"""
# ipmitool raw 0x3a 0x03 0x01 0x01 {register}
# register = 22 32 42 52 62 72 82
max_rpm = MAX_OUTLET if self.fan_index % 2 == 0 else MAX_INLET
fan1_ss_start = FAN1_FRONT_SS_ID if self.fan_index % 2 == 0 else FAN1_REAR_SS_ID
ss_id = hex(int(fan1_ss_start, 16) + self.fan_tray_index)
status, raw_ss_read = self._api_helper.ipmi_raw(
IPMI_SENSOR_NETFN, IPMI_FAN_SPEED_CMD.format(ss_id))
ss_read = raw_ss_read.split()[0]
rpm_speed = int(ss_read, 16)*150
speed = int(float(rpm_speed)/max_rpm * 100)
return speed
def get_target_speed(self):
"""
Retrieves the target (expected) speed of the fan
Returns:
An integer, the percentage of full fan speed, in the range 0 (off)
to 100 (full speed)
Note:
speed_pc = pwm_target/255*100
0 : when PWM mode is use
pwm : when pwm mode is not use
"""
target = 0
return target
def get_speed_tolerance(self):
"""
Retrieves the speed tolerance of the fan
Returns:
An integer, the percentage of variance from target speed which is
considered tolerable
"""
return SPEED_TOLERANCE
def set_speed(self, speed):
"""
Sets the fan speed
Args:
speed: An integer, the percentage of full fan speed to set fan to,
in the range 0 (off) to 100 (full speed)
Returns:
A boolean, True if speed is set successfully, False if not
Notes:
pwm setting mode must set as Manual
manual: ipmitool raw 0x3a 0x06 0x01 0x0
auto: ipmitool raw 0x3a 0x06 0x01 0x1
"""
# ipmitool raw 0x3a 0x03 0x01 0x02 {register} {pwm_speed}
# register = 22 32 42 52 62 72 82
speed_hex = hex(int(float(speed)/100 * 255))
fan_register_hex = hex(FAN_PWM_REGISTER_START +
(self.fan_tray_index*FAN_PWM_REGISTER_STEP))
set_speed_cmd = IPMI_SET_PWM.format(fan_register_hex, speed_hex)
status, set_speed_res = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, set_speed_cmd)
set_speed = False if not status else True
return set_speed
def set_status_led(self, color):
"""
Sets the state of the fan module status LED
Args:
color: A string representing the color with which to set the
fan module status LED
Returns:
bool: True if status LED state is set successfully, False if not
Note:
LED setting mode must set as Manual
manual: ipmitool raw 0x3A 0x09 0x02 0x00
auto: ipmitool raw 0x3A 0x09 0x02 0x01
"""
led_cmd = {
self.STATUS_LED_COLOR_GREEN: FAN_LED_GREEN_CMD,
self.STATUS_LED_COLOR_RED: FAN_LED_RED_CMD,
self.STATUS_LED_COLOR_OFF: FAN_LED_OFF_CMD
}.get(color)
fan_selector = hex(int(FAN1_LED_CMD, 16) + self.fan_tray_index)
status, set_led = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, IPMI_SET_FAN_LED_CMD.format(fan_selector, led_cmd))
set_status_led = False if not status else True
return set_status_led
def get_status_led(self):
"""
Gets the state of the fan status LED
Returns:
A string, one of the predefined STATUS_LED_COLOR_* strings above
Note:
STATUS_LED_COLOR_GREEN = "green"
STATUS_LED_COLOR_AMBER = "amber"
STATUS_LED_COLOR_RED = "red"
STATUS_LED_COLOR_OFF = "off"
"""
fan_selector = hex(int(FAN1_LED_CMD, 16) + self.fan_tray_index)
status, hx_color = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, IPMI_GET_FAN_LED_CMD.format(fan_selector))
status_led = {
"00": self.STATUS_LED_COLOR_OFF,
"01": self.STATUS_LED_COLOR_GREEN,
"02": self.STATUS_LED_COLOR_RED,
}.get(hx_color, self.STATUS_LED_COLOR_OFF)
return status_led
def get_name(self):
"""
Retrieves the name of the device
Returns:
string: The name of the device
"""
fan_name = FAN_NAME_LIST[self.fan_tray_index*2 + self.fan_index] if not self.is_psu_fan else "PSU-{} FAN-{}".format(
self.psu_index+1, self.fan_index+1)
return fan_name
def get_presence(self):
"""
Retrieves the presence of the FAN
Returns:
bool: True if FAN is present, False if not
"""
presence = False
status, raw_present = self._api_helper.ipmi_raw(
IPMI_OEM_NETFN, IPMI_FAN_PRESENT_CMD.format(hex(self.index)))
if status and raw_present == "00":
presence = True
return presence
def get_model(self):
"""
Retrieves the model number (or part number) of the device
Returns:
string: Model/part number of device
"""
model = "Unknown"
ipmi_fru_idx = self.fan_tray_index + FAN1_FRU_ID
status, raw_model = self._api_helper.ipmi_fru_id(
ipmi_fru_idx, IPMI_FRU_MODEL_KEY)
fru_pn_list = raw_model.split()
if len(fru_pn_list) > 4:
model = fru_pn_list[4]
return model
def get_serial(self):
"""
Retrieves the serial number of the device
Returns:
string: Serial number of device
"""
serial = "Unknown"
ipmi_fru_idx = self.fan_tray_index + FAN1_FRU_ID
status, raw_model = self._api_helper.ipmi_fru_id(
ipmi_fru_idx, IPMI_FRU_SERIAL_KEY)
fru_sr_list = raw_model.split()
if len(fru_sr_list) > 3:
serial = fru_sr_list[3]
return serial
def get_status(self):
"""
Retrieves the operational status of the device
Returns:
A boolean value, True if device is operating properly, False if not
"""
return self.get_presence() and self.get_speed() > 0

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python
import os
import subprocess
HOST_CHK_CMD = "docker > /dev/null 2>&1"
EMPTY_STRING = ""
class APIHelper():
def __init__(self):
pass
def is_host(self):
return os.system(HOST_CHK_CMD) == 0
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 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()
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()
except:
status = False
return status, result

View File

@ -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()

View File

@ -29,6 +29,8 @@ start)
fi
decode-syseeprom --init 2> /dev/null &
/bin/sh /usr/local/bin/platform_api_mgnt.sh init
echo "done."
;;

View File

@ -1,2 +1,4 @@
silverstone/cfg/silverstone-modules.conf etc/modules-load.d
silverstone/systemd/platform-modules-silverstone.service lib/systemd/system
silverstone/systemd/platform-modules-silverstone.service lib/systemd/system
silverstone/modules/sonic_platform-1.0-py2-none-any.whl usr/share/sonic/device/x86_64-cel_silverstone-r0
services/platform_api/platform_api_mgnt.sh usr/local/bin

View File

@ -1,3 +1,5 @@
depmod -a
systemctl enable platform-modules-silverstone.service
systemctl start platform-modules-silverstone.service
systemctl start platform-modules-silverstone.service
/usr/local/bin/platform_api_mgnt.sh install

View File

@ -0,0 +1,34 @@
from setuptools import setup
DEVICE_NAME = 'celestica'
HW_SKU = 'x86_64-cel_silverstone-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',
)