bedd05bb43
Fixes #6445 Because the ipmihelper.py script in the 9332 folder is slightly different than the common one (due to LGTM fixes), when the common one gets copied during build time it causes the workspace/build to become dirty. Signed-off-by: Danny Allen <daall@microsoft.com>
270 lines
8.8 KiB
Python
270 lines
8.8 KiB
Python
#!/usr/bin/python3
|
|
|
|
########################################################################
|
|
# DellEMC
|
|
#
|
|
# Module contains implementation of IpmiSensor and IpmiFru classes that
|
|
# provide Sensor's and FRU's information respectively.
|
|
#
|
|
########################################################################
|
|
|
|
import subprocess
|
|
import re
|
|
|
|
# IPMI Request Network Function Codes
|
|
NetFn_SensorEvent = 0x04
|
|
NetFn_Storage = 0x0A
|
|
|
|
# IPMI Sensor Device Commands
|
|
Cmd_GetSensorReadingFactors = 0x23
|
|
Cmd_GetSensorThreshold = 0x27
|
|
Cmd_GetSensorReading = 0x2D
|
|
|
|
# IPMI FRU Device Commands
|
|
Cmd_ReadFRUData = 0x11
|
|
|
|
def get_ipmitool_raw_output(args):
|
|
"""
|
|
Returns a list the elements of which are the individual bytes of
|
|
ipmitool raw <cmd> command output.
|
|
"""
|
|
result_bytes = list()
|
|
result = ""
|
|
command = "ipmitool raw {}".format(args)
|
|
try:
|
|
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
stdout = proc.communicate()[0]
|
|
proc.wait()
|
|
if not proc.returncode:
|
|
result = stdout.rstrip('\n')
|
|
except EnvironmentError:
|
|
pass
|
|
|
|
for i in result.split():
|
|
result_bytes.append(int(i, 16))
|
|
|
|
return result_bytes
|
|
|
|
class IpmiSensor(object):
|
|
|
|
# Sensor Threshold types and their respective bit masks
|
|
THRESHOLD_BIT_MASK = {
|
|
"LowerNonCritical" : 0,
|
|
"LowerCritical" : 1,
|
|
"LowerNonRecoverable" : 2,
|
|
"UpperNonCritical" : 3,
|
|
"UpperCritical" : 4,
|
|
"UpperNonRecoverable" : 5
|
|
}
|
|
|
|
def __init__(self, sensor_id, is_discrete=False):
|
|
self.id = sensor_id
|
|
self.is_discrete = is_discrete
|
|
|
|
def _get_converted_sensor_reading(self, raw_value):
|
|
"""
|
|
Returns a 2 element tuple(bool, int) in which first element
|
|
provides the validity of the reading and the second element is
|
|
the converted sensor reading
|
|
"""
|
|
# Get Sensor Reading Factors
|
|
cmd_args = "{} {} {} {}".format(NetFn_SensorEvent,
|
|
Cmd_GetSensorReadingFactors,
|
|
self.id, raw_value)
|
|
factors = get_ipmitool_raw_output(cmd_args)
|
|
|
|
if len(factors) != 7:
|
|
return False, 0
|
|
|
|
# Compute Twos complement
|
|
def get_twos_complement(val, bits):
|
|
if val & (1 << (bits - 1)):
|
|
val = val - (1 << bits)
|
|
return val
|
|
|
|
# Calculate actual sensor value from the raw sensor value
|
|
# using the sensor reading factors.
|
|
M = get_twos_complement(((factors[2] & 0xC0) << 8) | factors[1], 10)
|
|
B = get_twos_complement(((factors[4] & 0xC0) << 8) | factors[3], 10)
|
|
R_exp = get_twos_complement((factors[6] & 0xF0) >> 4, 4)
|
|
B_exp = get_twos_complement(factors[6] & 0x0F, 4)
|
|
|
|
converted_reading = ((M * raw_value) + (B * 10**B_exp)) * 10**R_exp
|
|
|
|
return True, converted_reading
|
|
|
|
def get_reading(self):
|
|
"""
|
|
For Threshold sensors, returns the sensor reading.
|
|
For Discrete sensors, returns the state value.
|
|
|
|
Returns:
|
|
A tuple (bool, int) where the first element provides the
|
|
validity of the reading and the second element provides the
|
|
sensor reading/state value.
|
|
"""
|
|
# Get Sensor Reading
|
|
cmd_args = "{} {} {}".format(NetFn_SensorEvent, Cmd_GetSensorReading,
|
|
self.id)
|
|
output = get_ipmitool_raw_output(cmd_args)
|
|
if len(output) != 4:
|
|
return False, 0
|
|
|
|
# Check reading/state unavailable
|
|
if output[1] & 0x20:
|
|
return False, 0
|
|
|
|
if self.is_discrete:
|
|
state = ((output[3] & 0x7F) << 8) | output[2]
|
|
return True, state
|
|
else:
|
|
return self._get_converted_sensor_reading(output[0])
|
|
|
|
def get_threshold(self, threshold_type):
|
|
"""
|
|
Returns the sensor's threshold value for a given threshold type.
|
|
|
|
Args:
|
|
threshold_type (str) - one of the below mentioned
|
|
threshold type strings
|
|
|
|
"LowerNonCritical"
|
|
"LowerCritical"
|
|
"LowerNonRecoverable"
|
|
"UpperNonCritical"
|
|
"UpperCritical"
|
|
"UpperNonRecoverable"
|
|
Returns:
|
|
A tuple (bool, int) where the first element provides the
|
|
validity of that threshold and second element provides the
|
|
threshold value.
|
|
"""
|
|
# Thresholds are not valid for discrete sensors
|
|
if self.is_discrete:
|
|
raise TypeError("Threshold is not applicable for Discrete Sensor")
|
|
|
|
if threshold_type not in list(self.THRESHOLD_BIT_MASK.keys()):
|
|
raise ValueError("Invalid threshold type {} provided. Valid types "
|
|
"are {}".format(threshold_type,
|
|
list(self.THRESHOLD_BIT_MASK.keys())))
|
|
|
|
bit_mask = self.THRESHOLD_BIT_MASK[threshold_type]
|
|
|
|
# Get Sensor Threshold
|
|
cmd_args = "{} {} {}".format(NetFn_SensorEvent, Cmd_GetSensorThreshold,
|
|
self.id)
|
|
thresholds = get_ipmitool_raw_output(cmd_args)
|
|
if len(thresholds) != 7:
|
|
return False, 0
|
|
|
|
valid_thresholds = thresholds.pop(0)
|
|
# Check whether particular threshold is readable
|
|
if valid_thresholds & (1 << bit_mask):
|
|
return self._get_converted_sensor_reading(thresholds[bit_mask])
|
|
else:
|
|
return False, 0
|
|
|
|
class IpmiFru(object):
|
|
|
|
def __init__(self, fru_id):
|
|
self.id = fru_id
|
|
|
|
def _get_ipmitool_fru_print(self):
|
|
result = ""
|
|
command = "ipmitool fru print {}".format(self.id)
|
|
try:
|
|
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
stdout = proc.communicate()[0]
|
|
proc.wait()
|
|
if not proc.returncode:
|
|
result = stdout.decode('utf-8').rstrip('\n')
|
|
except EnvironmentError:
|
|
pass
|
|
|
|
return result
|
|
|
|
def _get_from_fru(self, info):
|
|
"""
|
|
Returns a string containing the info from FRU
|
|
"""
|
|
fru_output = self._get_ipmitool_fru_print()
|
|
if not fru_output:
|
|
return "NA"
|
|
|
|
info_req = re.search(r"%s\s*:(.*)" % info, fru_output)
|
|
if not info_req:
|
|
return "NA"
|
|
|
|
return info_req.group(1).strip()
|
|
|
|
def get_board_serial(self):
|
|
"""
|
|
Returns a string containing the Serial Number of the device.
|
|
"""
|
|
return self._get_from_fru('Board Serial')
|
|
|
|
def get_board_part_number(self):
|
|
"""
|
|
Returns a string containing the Part Number of the device.
|
|
"""
|
|
return self._get_from_fru('Board Part Number')
|
|
|
|
def get_board_mfr_id(self):
|
|
"""
|
|
Returns a string containing the manufacturer id of the FRU.
|
|
"""
|
|
return self._get_from_fru('Board Mfg')
|
|
|
|
def get_board_product(self):
|
|
"""
|
|
Returns a string containing the manufacturer id of the FRU.
|
|
"""
|
|
return self._get_from_fru('Board Product')
|
|
|
|
def get_fru_data(self, offset, count=1):
|
|
"""
|
|
Reads and returns the FRU data at the provided offset.
|
|
|
|
Args:
|
|
offset (int) - FRU offset to read
|
|
count (int) - Number of bytes to read [optional, default = 1]
|
|
Returns:
|
|
A tuple (bool, list(int)) where the first element provides
|
|
the validity of the data read and the second element is a
|
|
list, the elements of which are the individual bytes of the
|
|
FRU data read.
|
|
"""
|
|
result_bytes = list()
|
|
is_valid = True
|
|
result = ""
|
|
|
|
offset_LSB = offset & 0xFF
|
|
offset_MSB = offset & 0xFF00
|
|
command = "ipmitool raw {} {} {} {} {} {}".format(NetFn_Storage,
|
|
Cmd_ReadFRUData,
|
|
self.id, offset_LSB,
|
|
offset_MSB, count)
|
|
try:
|
|
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
stdout = proc.communicate()[0]
|
|
proc.wait()
|
|
if not proc.returncode:
|
|
result = stdout.decode('utf-8').rstrip('\n')
|
|
except EnvironmentError:
|
|
is_valid = False
|
|
|
|
if (not result) or (not is_valid):
|
|
return False, result_bytes
|
|
|
|
for i in result.split():
|
|
result_bytes.append(int(i, 16))
|
|
|
|
read_count = result_bytes.pop(0)
|
|
if read_count != count:
|
|
return False, result_bytes
|
|
else:
|
|
return True, result_bytes
|