2
0
mirror of https://opendev.org/x/pyghmi synced 2026-04-01 07:43:39 +00:00

Merge "Support RS160"

This commit is contained in:
Zuul
2021-09-10 15:53:58 +00:00
committed by Gerrit Code Review
8 changed files with 297 additions and 83 deletions

View File

@@ -27,9 +27,10 @@ import pyghmi.ipmi.events as sel
import pyghmi.ipmi.fru as fru
import pyghmi.ipmi.oem.generic as genericoem
from pyghmi.ipmi.oem.lookup import get_oem_handler
import pyghmi.ipmi.private.util as pygutil
import pyghmi.ipmi.private.util as util
from pyghmi.ipmi import sdr
try:
from pyghmi.ipmi.private import session
except ImportError:
@@ -648,18 +649,6 @@ class Command(object):
return self._oem.get_inventory_of_component(component)
def _get_zero_fru(self):
# It is expected that a manufacturer matches SMBIOS to IPMI
# get system uuid return data. If a manufacturer does not
# do so, they should handle either deletion or fixup in the
# OEM processing pass. Code optimistically assumes that if
# data is returned, than the vendor is properly using it.
zerofru = fru.FRU(ipmicmd=self).info
if zerofru is None:
zerofru = {}
guiddata = self.raw_command(netfn=6, command=0x37)
if 'error' not in guiddata:
zerofru['UUID'] = pygutil.decode_wireformat_uuid(
guiddata['data'])
# Add some fields returned by get device ID command to FRU 0
# Also rename them to something more in line with FRU 0 field naming
# standards
@@ -668,8 +657,21 @@ class Command(object):
device_id['Device Revision'] = device_id.pop('device_revision')
device_id['Manufacturer ID'] = device_id.pop('manufacturer_id')
device_id['Product ID'] = device_id.pop('product_id')
zerofru = fru.FRU(ipmicmd=self).info
if zerofru is None:
zerofru = {}
zerofru.update(device_id)
return self._oem.process_fru(zerofru)
zerofru = self._oem.process_zero_fru(zerofru)
# If uuid is not returned in OEM processing,
# then it is expected that a manufacturer matches SMBIOS to IPMI
# get system uuid return data.
if 'UUID' not in zerofru:
guiddata = self.ipmicmd.raw_command(netfn=6, command=0x37)
if 'error' not in guiddata:
zerofru['UUID'] = util.\
decode_wireformat_uuid(guiddata['data'])
return zerofru
def get_inventory(self):
"""Retrieve inventory of system

View File

@@ -396,3 +396,6 @@ class OEMHandler(object):
:param uid: User ID.
"""
return False
def process_zero_fru(self, zerofru):
return self.process_fru(zerofru)

View File

@@ -26,6 +26,17 @@ cpu_fields = (
valuefunc=lambda v: str(v) + " MHz"),
inventory.EntryField("Reserved", "h", include=False))
cpu_cmd = {
"lenovo": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc1, 0x01, 0x00)},
"asrock": {
"netfn": 0x3a,
"command": 0x50,
"data": (0x01, 0x01, 0x00)},
}
def parse_cpu_info(raw):
return inventory.parse_inventory_category_entry(raw, cpu_fields)
@@ -36,10 +47,6 @@ def get_categories():
"cpu": {
"idstr": "CPU {0}",
"parser": parse_cpu_info,
"command": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc1, 0x01, 0x00)
}
"command": cpu_cmd
}
}

View File

@@ -32,6 +32,17 @@ dimm_fields = (
inventory.EntryField("reserved", "h", include=False)
)
dimm_cmd = {
"lenovo": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc1, 0x02, 0x00)},
"asrock": {
"netfn": 0x3a,
"command": 0x50,
"data": (0x01, 0x02, 0x01)},
}
def parse_dimm_info(raw):
return inventory.parse_inventory_category_entry(raw, dimm_fields)
@@ -42,11 +53,7 @@ def get_categories():
"dimm": {
"idstr": "DIMM {0}",
"parser": parse_dimm_info,
"command": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc1, 0x02, 0x00)
},
"command": dimm_cmd,
"workaround_bmc_bug": True
}
}

View File

@@ -33,19 +33,69 @@ firmware_fields = (
inventory.EntryField("WIND", "16s"),
inventory.EntryField("DIAG", "16s"))
asrock_firmware_fields = (
inventory.EntryField("Major Firmware Revision", "B"),
inventory.EntryField("Minor Firmware Revision", "B"),
inventory.EntryField("Auxiliary Firmware Revision", "4s"))
def parse_firmware_info(raw, bios_versions=None):
bytes_read, data = inventory.parse_inventory_category_entry(
raw, firmware_fields)
del data['Revision']
for key in data:
yield key, {'version': data[key]}
firmware_cmd = {
"lenovo": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc7, 0x00, 0x00)},
"asrock": {
"netfn": 0x3a,
"command": 0x50,
"data": (0x02, 0x00, 0x01)},
}
if bios_versions is not None:
yield ("Bios_bundle_ver",
{'version': bios_versions['new_img_version']})
yield ("Bios_current_ver",
{'version': bios_versions['cur_img_version']})
bios_cmd = {
"lenovo": {
"netfn": 0x32,
"command": 0xE8,
"data": (0x01, 0x01, 0x02)},
"asrock": {
"netfn": 0x3a,
"command": 0x50,
"data": (0x02, 0x01, 0x01)},
}
def parse_firmware_info(raw, bios_versions=None, asrock=False):
fields = firmware_fields
if asrock:
fields = asrock_firmware_fields
bytes_read, data = inventory.parse_inventory_category_entry(raw, fields)
if asrock:
major_version = data['Major Firmware Revision']
minor_version = data['Minor Firmware Revision']
# Asrock RS160 the minor version is Binary Coded Decimal,
# convert it to Decimal
minor_version = (0xff & (minor_version >> 4)) * 10 + \
(0xf & minor_version)
aux_reversion = 0
if str(data['Auxiliary Firmware Revision']) != '':
aux_reversion = ord(data['Auxiliary Firmware Revision'])
bmc_version = "%s.%s.%s" % (
str(major_version),
str(minor_version),
str(aux_reversion))
yield ("BMC", {'version': bmc_version})
if bios_versions is not None:
yield ("Bios", {'version': bios_versions[0:]})
else:
del data["Revision"]
for key in data:
yield (key, {'version': data[key]})
if bios_versions is not None:
yield ("Bios_bundle_ver",
{'version': bios_versions['new_img_version']})
yield ("Bios_current_ver",
{'version': bios_versions['cur_img_version']})
def parse_bios_number(raw):
@@ -57,20 +107,11 @@ def get_categories():
"firmware": {
"idstr": "FW Version",
"parser": parse_firmware_info,
"command": {
"netfn": 0x06,
"command": 0x59,
"data": (0x00, 0xc7, 0x00, 0x00)
}
"command": firmware_cmd
},
"bios_version": {
"idstr": "Bios Version",
"parser": parse_bios_number,
"command": {
"netfn": 0x32,
"command": 0xE8,
"data": (0x01, 0x01, 0x02)
}
"command": bios_cmd
}
}

View File

@@ -123,8 +123,23 @@ led_status = {
0x00: "Off",
0xFF: "On"
}
asrock_leds = {
"SYSTEM_EVENT": 0x00,
"BMC_UID": 0x01,
"LED_FAN_FAULT_1": 0x02,
"LED_FAN_FAULT_2": 0x03,
"LED_FAN_FAULT_3": 0x04
}
asrock_led_status = {
0x00: "Off",
0x01: "On"
}
led_status_default = "Blink"
mac_format = '{0:02x}:{1:02x}:{2:02x}:{3:02x}:{4:02x}:{5:02x}'
categorie_items = ["cpu", "dimm", "firmware", "bios_version"]
def _megarac_abbrev_image(name):
@@ -289,7 +304,7 @@ class OEMHandler(generic.OEMHandler):
return super(OEMHandler, self).reseat_bay(bay)
def get_ntp_enabled(self):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
ntpres = self.ipmicmd.xraw_command(netfn=0x32, command=0xa7)
return ntpres['data'][0] == '\x01'
elif self.is_fpc:
@@ -299,7 +314,7 @@ class OEMHandler(generic.OEMHandler):
return None
def get_ntp_servers(self):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
srvs = []
ntpres = self.ipmicmd.xraw_command(netfn=0x32, command=0xa7)
srvs.append(ntpres['data'][1:129].rstrip('\x00'))
@@ -312,7 +327,7 @@ class OEMHandler(generic.OEMHandler):
return None
def set_ntp_enabled(self, enabled):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
if enabled:
self.ipmicmd.xraw_command(
netfn=0x32, command=0xa8, data=(3, 1), timeout=15)
@@ -328,7 +343,7 @@ class OEMHandler(generic.OEMHandler):
return None
def set_ntp_server(self, server, index=0):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
if not (0 <= index <= 1):
raise pygexc.InvalidParameterValue("Index must be 0 or 1")
cmddata = bytearray((1 + index, ))
@@ -405,8 +420,29 @@ class OEMHandler(generic.OEMHandler):
return True
return False
@property
def has_asrock(self):
# True if this particular server have a ASROCKRACK
# based service processor (RS160 or TS460)
# RS160 (Riddler) product id is 1182 (049Eh)
# TS460 (WildThing) product id is 1184 (04A0h)
if (self.oemid['manufacturer_id'] == 19046
and (self.oemid['product_id'] == 1182
or self.oemid['product_id'] == 1184)):
try:
self.ipmicmd.xraw_command(netfn=0x3a,
command=0x50,
data=(0x00, 0x00, 0x00))
except pygexc.IpmiException as ie:
if ie.ipmicode == 193:
return False
raise
return True
return False
def get_oem_inventory_descriptions(self):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
# Thinkserver with TSM
if not self.oem_inventory_info:
self._collect_tsm_inventory()
@@ -418,7 +454,7 @@ class OEMHandler(generic.OEMHandler):
return ()
def get_oem_inventory(self):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
self._collect_tsm_inventory()
for compname in self.oem_inventory_info:
yield (compname, self.oem_inventory_info[compname])
@@ -460,7 +496,7 @@ class OEMHandler(generic.OEMHandler):
return ()
def get_inventory_of_component(self, component):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
self._collect_tsm_inventory()
return self.oem_inventory_info.get(component, None)
if self.has_imm:
@@ -468,24 +504,46 @@ class OEMHandler(generic.OEMHandler):
if self.is_fpc:
return self.smmhandler.get_inventory_of_component(component)
def get_cmd_type(self, categorie_item, catspec):
if self.has_asrock:
cmd_type = catspec["command"]["asrock"]
elif categorie_item in categorie_items:
cmd_type = catspec["command"]["lenovo"]
else:
cmd_type = catspec["command"]
return cmd_type
def _collect_tsm_inventory(self):
self.oem_inventory_info = {}
asrock = False
if self.has_asrock:
asrock = True
for catid, catspec in inventory.categories.items():
if (catspec.get("workaround_bmc_bug", False)):
# skip the inventory fields if the system is RS160
if asrock and catid not in categorie_items:
continue
if (catspec.get("workaround_bmc_bug", False)
and catspec["workaround_bmc_bug"](
"ami" if self.has_ami else "lenovo")):
rsp = None
tmp_command = dict(catspec["command"])
cmd = self.get_cmd_type(catid, catspec)
tmp_command = dict(cmd)
tmp_command["data"] = list(tmp_command["data"])
count = 0
for i in range(0x01, 0xff):
tmp_command["data"][-1] = i
try:
partrsp = self.ipmicmd.xraw_command(**tmp_command)
count += 1
if asrock and partrsp["data"][1] == "\xff":
continue
if rsp is None:
rsp = partrsp
rsp["data"] = list(rsp["data"])
else:
rsp["data"].extend(partrsp["data"][1:])
count += 1
except Exception:
break
# If we didn't get any response, assume we don't have
@@ -496,13 +554,14 @@ class OEMHandler(generic.OEMHandler):
rsp["data"] = buffer(bytearray(rsp["data"]))
else:
try:
rsp = self.ipmicmd.xraw_command(**catspec["command"])
cmd = self.get_cmd_type(catid, catspec)
rsp = self.ipmicmd.xraw_command(**cmd)
except pygexc.IpmiException:
continue
# Parse the response we got
try:
items = inventory.parse_inventory_category(
catid, rsp,
catid, rsp, asrock,
countable=catspec.get("countable", True)
)
except Exception:
@@ -512,7 +571,22 @@ class OEMHandler(generic.OEMHandler):
for item in items:
try:
key = catspec["idstr"].format(item["index"])
# Originally on ThinkServer and SD350 (Kent),
# the DIMM is distinguished by slot,
# the key is the value of slot number (item["index"])
# While on RS160/TS460 the DIMMs is distinguished
# by slot number and channel number,
# the key is the value of the sum of slot number
# and channel number
if asrock and catid == "dimm":
if item["channel_number"] == 1:
key = catspec["idstr"].format(item["index"])
else:
key = catspec["idstr"].format(
item["index"] + item["channel_number"])
else:
key = catspec["idstr"].format(item["index"])
del item["index"]
self.oem_inventory_info[key] = item
except Exception:
@@ -521,16 +595,34 @@ class OEMHandler(generic.OEMHandler):
continue
def get_leds(self):
if self.has_tsm:
for (name, id_) in leds.items():
try:
rsp = self.ipmicmd.xraw_command(netfn=0x3A, command=0x02,
cmd = 0x02
led_set = leds
led_set_status = led_status
asrock = self.has_asrock
if asrock:
cmd = 0x50
led_set = asrock_leds
led_set_status = asrock_led_status
for (name, id_) in led_set.items():
try:
if asrock:
rsp = self.ipmicmd.xraw_command(netfn=0x3A, command=cmd,
data=(0x03, id_, 0x00))
rdata = bytearray(rsp['data'][:])
status = rdata[1]
else:
rsp = self.ipmicmd.xraw_command(netfn=0x3A, command=cmd,
data=(id_,))
except pygexc.IpmiException:
continue # Ignore LEDs we can't retrieve
status = led_status.get(bytearray(rsp['data'][:])[0],
rdata = bytearray(rsp['data'][:])
status = rdata[0]
except pygexc.IpmiException:
continue # Ignore LEDs we can't retrieve
status = led_set_status.get(status,
led_status_default)
yield (name, {'status': status})
yield (name, {'status': status})
def set_identify(self, on, duration):
if on and not duration and self.is_sd350:
@@ -608,6 +700,17 @@ class OEMHandler(generic.OEMHandler):
elif self.is_fpc and self.is_fpc != 6: # SMM variant
fru['oem_parser'] = 'lenovo'
return self.smmhandler.process_fru(fru)
elif self.has_asrock:
fru['oem_parser'] = 'lenovo'
# ASRock RS160 TS460 lays out specific interpretation of the
# board extra fields
try:
mac1 = fru['board_extra']
if mac1 not in ('00:00:00:00:00:00', ''):
fru['MAC Address 1'] = mac1.encode('utf-8')
except (AttributeError, KeyError):
pass
return fru
else:
fru['oem_parser'] = None
return fru
@@ -653,20 +756,27 @@ class OEMHandler(generic.OEMHandler):
return self._hasimm
def get_oem_firmware(self, bmcver, components):
if self.has_tsm:
if self.has_tsm or self.has_asrock:
command = firmware.get_categories()["firmware"]
rsp = self.ipmicmd.xraw_command(**command["command"])
fw_cmd = self.get_cmd_type("firmware", command)
rsp = self.ipmicmd.xraw_command(**fw_cmd)
# the newest Lenovo ThinkServer versions are returning Bios version
# numbers through another command
bios_versions = None
if self.has_tsm:
if self.has_tsm or self.has_asrock:
bios_command = firmware.get_categories()["bios_version"]
bios_rsp = self.ipmicmd.xraw_command(**bios_command["command"])
bios_versions = bios_command["parser"](bios_rsp["data"])
bios_cmd = self.get_cmd_type("bios_version", bios_command)
bios_rsp = self.ipmicmd.xraw_command(**bios_cmd)
if self.has_asrock:
bios_versions = bios_rsp['data']
else:
bios_versions = bios_command["parser"](bios_rsp['data'])
# pass bios versions to firmware parser
return command["parser"](rsp["data"], bios_versions)
return command["parser"](rsp["data"],
bios_versions,
self.has_asrock)
elif self.has_imm:
return self.immhandler.get_firmware_inventory(bmcver, components)
elif self.is_fpc:
@@ -1133,3 +1243,37 @@ class OEMHandler(generic.OEMHandler):
uid, 0x03, 0x00, 0x00, 0x00))
return True
return False
def process_zero_fru(self, zerofru):
if (self.oemid['manufacturer_id'] == 19046
and self.oemid['product_id'] == 13616):
# Currently SD350 FRU UUID is synchronized with the Device UUID.
# Need to change to System UUID in future.
# Since the IPMI get device uuid matches SMBIOS,
# no need to decode it.
guiddata = self.ipmicmd.raw_command(netfn=6, command=0x8)
if 'error' not in guiddata:
zerofru['UUID'] = util.decode_wireformat_uuid(
guiddata['data'], True)
else:
# It is expected that a manufacturer matches SMBIOS to IPMI
# get system uuid return data. If a manufacturer does not
# do so, they should handle either deletion or fixup in the
# OEM processing pass. Code optimistically assumes that if
# data is returned, than the vendor is properly using it.
guiddata = self.ipmicmd.raw_command(netfn=6, command=0x37)
if 'error' not in guiddata:
if (self.oemid['manufacturer_id'] == 19046
and (self.oemid['product_id'] == 1182
or self.oemid['product_id'] == 1184)):
# The manufacturer (Asrockrack) of RS160/TS460
# matches SMBIOS
# to IPMI get system uuid return data,
# no need to decode it.
zerofru['UUID'] = util.decode_wireformat_uuid(
guiddata['data'], True)
else:
zerofru['UUID'] = util.decode_wireformat_uuid(
guiddata['data'])
return self.process_fru(zerofru)

View File

@@ -52,7 +52,7 @@ class EntryField(object):
# General parameter parsing functions
def parse_inventory_category(name, info, countable=True):
def parse_inventory_category(name, info, asrock=False, countable=True):
"""Parses every entry in an inventory category
For example: CPU, memory, PCI, drives
@@ -61,11 +61,15 @@ def parse_inventory_category(name, info, countable=True):
:param name: the name of the parameter (e.g.: "cpu")
:param info: a list of integers with raw data read from an IPMI requests
:param asrock: a boolean represents if RS160 with asrockrack or not
:param countable: whether the data have an entries count field
:returns: dict -- a list of entries in the category.
"""
raw = info["data"][1:]
if name == "cpu" and asrock:
raw = info["data"]
cur = 0
if countable:
count = bytearray(raw)[cur]
@@ -76,17 +80,17 @@ def parse_inventory_category(name, info, countable=True):
entries = []
while cur < len(raw):
read, cpu = categories[name]["parser"](raw[cur:])
read, parser = categories[name]["parser"](raw[cur:])
cur = cur + read
# Account for discarded entries (because they are not present)
if cpu is None:
if parser is None:
discarded += 1
continue
if not countable:
# count by myself
count += 1
cpu["index"] = count
entries.append(cpu)
parser["index"] = count
entries.append(parser)
# TODO(avidal): raise specific exception to point that there's data left in
# the buffer
@@ -94,7 +98,7 @@ def parse_inventory_category(name, info, countable=True):
raise Exception
# TODO(avidal): raise specific exception to point that the number of
# entries is different than the expected
if count - discarded != len(entries):
if count - discarded != len(entries) and not asrock:
raise Exception
return entries
@@ -119,6 +123,9 @@ def parse_inventory_category_entry(raw, fields):
value = struct.unpack_from(field.fmt, r)[0]
read = struct.calcsize(field.fmt)
bytes_read += read
if bytes_read > len(raw):
break
r = r[read:]
# If this entry is not actually present, just parse and then discard it
if field.presence and not bool(value):
@@ -127,7 +134,7 @@ def parse_inventory_category_entry(raw, fields):
continue
if (field.fmt[-1] == "s"):
value = value.rstrip(b'\x00')
value = value.rstrip(b'\x00\xff')
if (field.mapper and value in field.mapper):
value = field.mapper[value]
if (field.valuefunc):

View File

@@ -37,7 +37,7 @@ except AttributeError:
pass
def decode_wireformat_uuid(rawguid):
def decode_wireformat_uuid(rawguid, bigendian=False):
"""Decode a wire format UUID
It handles the rather particular scheme where half is little endian
@@ -45,7 +45,10 @@ def decode_wireformat_uuid(rawguid):
"""
if isinstance(rawguid, list):
rawguid = bytearray(rawguid)
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
endian = '<IHH' # little endian
if bigendian:
endian = '>IHH' # big endian
lebytes = struct.unpack_from(endian, buffer(rawguid[:8]))
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2])