2
0
mirror of https://opendev.org/x/pyghmi synced 2026-05-13 18:04:19 +00:00

Add support for retrieving SDR data

This patch implements SDR retrieval and the foundation to use the data
to translate raw sensor reading data to usable information.

Change-Id: Ic77fcde6a283a2ee7745a9c159038d2655911b0a
This commit is contained in:
Jarrod Johnson
2014-01-16 16:06:36 -05:00
parent 0897e0d408
commit 3a4338365b
4 changed files with 823 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Health:
Ok = 0
Warning, Critical, Failed = [2**x for x in range(0, 3)]
+5
View File
@@ -28,3 +28,8 @@ class IpmiException(PyghmiException):
class InvalidParameterValue(PyghmiException):
pass
class BmcErrorException(IpmiException):
# This denotes when library detects an invalid BMC behavior
pass
+198
View File
@@ -26,6 +26,204 @@ payload_types = {
'rakp4': 0x15,
}
#sensor type codes, table 42-3
sensor_type_codes = {
1: 'Temperature',
2: 'Voltage',
3: 'Current',
4: 'Fan',
5: 'Chassis Intrusion',
6: 'Platform Security',
7: 'Processor',
8: 'Power Supply',
9: 'Power Unit',
0xa: 'Cooling Device',
0xb: 'Other',
0xc: 'Memory',
0xd: 'Drive Bay',
0xe: 'POST Memory Resize',
0xf: 'System Firmware Progress',
0x10: 'Event Log Disabled',
0x11: 'Watchdog',
0x12: 'System Event',
0x13: 'Critical interrupt',
0x14: 'Button/switch',
0x15: 'Module/Board',
0x16: 'Microcontroller/Coprocessor',
0x17: 'Add-in Card',
0x18: 'Chassis',
0x19: 'Chip Set',
0x1a: 'Other FRU',
0x1b: 'Cable/Interconnect',
0x1c: 'Terminator',
0x1d: 'System Boot',
0x1e: 'Boot Error',
0x1f: 'OS Boot',
0x20: 'OS Stop',
0x21: 'Slot/Connector',
0x22: 'System ACPI Power State',
0x23: 'Watchdog',
0x24: 'Platform alert',
0x25: 'Entity Presence',
0x26: 'Monitor ASIC/IC',
0x27: 'LAN',
0x28: 'Management Subsystem Health',
0x29: 'Battery',
0x2a: 'Session Audit',
0x2b: 'Version Change',
0x2c: 'FRU State',
}
sensor_type_offsets = {
1: 'Temperature',
2: 'Voltage',
3: 'Current',
4: 'Fan',
5: {
0: 'General Chassis Intrusion',
1: 'Drive Bay intrusion',
2: 'I/O Card area intrusion',
3: 'Processor area intrusion',
4: 'Lost LAN connection',
5: 'Unauthorized dock',
6: 'Fan area intrusion',
},
6: {
0: 'Front Panel Lockout Violation attempt',
1: 'Pre-boot password violation - user',
2: 'Pre-boot password violation - setup',
3: 'Pre-boot password violation - netboot',
4: 'Pre-boot password violation',
5: 'Out-of-band access password violation',
},
7: {
0: 'processor IERR',
1: 'processor thermal trip',
2: 'processor FRB1/BIST failure',
3: 'processor FRB2/Hang in POST failure',
4: 'processor FRB3/processor startup failure',
5: 'processor configuration error',
6: 'uncorrectable cpu complex error',
7: 'processor presence detected',
8: 'processor disabled',
9: 'processor terminator presence detected',
0xa: 'processor throttled',
0xb: 'uncorrectable machine check exception',
0xc: 'correctable machine check exception',
},
8: {
0: 'power supply presence detected',
1: 'power supply failure',
2: 'power supply predictive failure',
3: 'power supply input lost',
4: 'power supply input out of range or lost',
5: 'power supply input out of range',
6: 'power supply configuration error', # event data 3 available
},
9: {
0: 'power off/down',
1: 'power cycle',
2: '240VA power down',
3: 'interlock power down',
4: 'power input lost',
5: 'soft power control failure',
6: 'power unit failure',
7: 'power unit predictive failure',
},
0xa: 'cooling device',
0xb: 'units based sensor',
0xc: {
0: 'correctable memory error',
1: 'uncorrectable memory error',
2: 'memory parity',
3: 'memory scrub failed',
4: 'memory device disabled',
5: 'correctable memory error logging limit reached',
6: 'memory presence detected',
7: 'memory configuration error',
8: 'spare memory', # event data 3 available
9: 'memory throttled',
0xa: 'critical memory overtemperature',
},
0xd: {
0: 'drive presence',
1: 'drive fault',
2: 'predictive drive failure',
3: 'hot spare drive',
4: 'drive consitency check in progress',
5: 'drive in critical array',
6: 'drive in failed array',
7: 'rebuild in progress',
8: 'rebuild aborted',
},
0xe: 'POST memory resize',
}
#entity ids from table 43-13 entity id codes
entity_ids = {
0x0: 'unspecified',
0x1: 'other',
0x2: 'unknown',
0x3: 'processor',
0x4: 'disk or disk bay',
0x5: 'peripheral bay',
0x6: 'system management module',
0x7: 'system board',
0x8: 'memory module',
0x9: 'processor module',
0xa: 'power supply',
0xb: 'add-in card',
0xc: 'front panel board',
0xd: 'back panel board',
0xe: 'power system board',
0xf: 'drive backplane',
0x10: 'system internal expansion board',
0x11: 'other system board',
0x12: 'processor board',
0x13: 'power unit / power domain',
0x14: 'power module / DC-to-DC converter',
0x15: 'power management /power distribution board',
0x16: 'chassis back panel board',
0x17: 'system chassis',
0x18: 'sub-chassis',
0x19: 'other chassis board',
0x1a: 'disk drive bay',
0x1b: 'peripheral bay',
0x1c: 'device bay',
0x1d: 'fan/cooling device',
0x1e: 'cooling unit / cooling domain',
0x1f: 'cable / interconnect',
0x20: 'memory device',
0x21: 'system management software',
0x22: 'system firmware',
0x23: 'operating system',
0x24: 'system bus',
0x25: 'group',
0x26: 'remote management communication device',
0x27: 'external environment',
0x28: 'battery',
0x29: 'processing blade',
0x2a: 'connectivity switch',
0x2b: 'processor/memory module',
0x2c: 'I/O module',
0x2d: 'Processor I/O module',
0x2e: 'management controller firmware',
0x2f: 'IPMI channel',
0x30: 'PCI Bus',
0x31: 'PCIe Bus',
0x32: 'SCSI Bus',
0x33: 'SATA/SAS Bus',
0x34: 'processor / front-side bus',
0x35: 'real time clock',
0x37: 'air inlet',
0x40: 'air inlet',
0x41: 'processor',
0x42: 'system board',
}
rmcp_codes = {
1: ("Insufficient resources to create new session (wait for existing "
"sessions to timeout)"),
+600
View File
@@ -0,0 +1,600 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf8
# Copyright 2013 IBM Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This module provides access to SDR offered by a BMC
# This data is common between 'sensors' and 'inventory' modules since SDR
# is both used to enumerate sensors for sensor commands and FRU ids for FRU
# commands
# For now, we will not offer persistent SDR caching as we do in xCAT's IPMI
# code. Will see if it is adequate to advocate for high object reuse in a
# persistent process for the moment.
# Focus is at least initially on the aspects that make the most sense for a
# remote client to care about. For example, smbus information is being
# skipped for now
import math
import pyghmi.constants as const
import pyghmi.exceptions as exc
import pyghmi.ipmi.command as ipmicmd
import pyghmi.ipmi.private.constants as ipmiconstants
import struct
TYPE_UNKNOWN = 0
TYPE_SENSOR = 1
TYPE_FRU = 2
def ones_complement(value, bits):
# utility function to help with the large amount of 2s
# complement prevalent in ipmi spec
signbit = 0b1 << (bits - 1)
if value & signbit:
#if negative, subtract 1, then take 1s
#complement given bits width
return 0 - (value ^ ((0b1 << bits) - 1))
else:
return value
def twos_complement(value, bits):
# utility function to help with the large amount of 2s
# complement prevalent in ipmi spec
signbit = 0b1 << (bits - 1)
if value & signbit:
#if negative, subtract 1, then take 1s
#complement given bits width
return 0 - ((value - 1) ^ ((0b1 << bits) - 1))
else:
return value
unit_types = {
# table 43-15 'sensor unit type codes'
0: '',
1: '°C',
2: '°F',
3: 'K',
4: 'V',
5: 'A',
6: 'W',
7: 'J',
8: 'C',
9: 'VA',
10: 'nt',
11: 'lm',
12: 'lx',
13: 'cd',
14: 'kPa',
15: 'PSI',
16: 'N',
17: 'CFM',
18: 'RPM',
19: 'Hz',
20: 'μs',
21: 'ms',
22: 's',
23: 'min',
24: 'hr',
25: 'd',
26: 'week(s)',
27: 'mil',
28: 'inches',
29: 'ft',
30: 'cu in',
31: 'cu feet',
32: 'mm',
33: 'cm',
34: 'm',
35: 'cu cm',
36: 'cu m',
37: 'L',
38: 'fl. oz.',
39: 'radians',
40: 'steradians',
41: 'revolutions',
42: 'cycles',
43: 'g',
44: 'ounce',
45: 'lb',
46: 'ft-lb',
47: 'oz-in',
48: 'gauss',
49: 'gilberts',
50: 'henry',
51: 'millihenry',
52: 'farad',
53: 'microfarad',
54: 'ohms',
55: 'siemens',
56: 'mole',
57: 'becquerel',
58: 'ppm',
60: 'dB',
61: 'dBA',
62: 'dBC',
63: 'Gy',
64: 'sievert',
65: 'color temp deg K',
66: 'bit',
67: 'kb',
68: 'mb',
69: 'gb',
70: 'byte',
71: 'kB',
72: 'mB',
73: 'gB',
74: 'word',
75: 'dword',
76: 'qword',
77: 'line',
78: 'hit',
79: 'miss',
80: 'retry',
81: 'reset',
82: 'overrun/overflow',
83: 'underrun',
84: 'collision',
85: 'packets',
86: 'messages',
87: 'characters',
88: 'error',
89: 'uncorrectable error',
90: 'correctable error',
91: 'fatal error',
92: 'grams',
}
sensor_rates = {
0: '',
1: ' per us',
2: ' per ms',
3: ' per s',
4: ' per minute',
5: ' per hour',
6: ' per day',
}
class SensorReading(object):
"""Representation of the state of a sensor.
It is initialized by pyghmi internally, it does not make sense for
a developer to create one of these objects directly.
It provides the following properties:
name: UTF-8 string describing the sensor
units: UTF-8 string describing the units of the sensor (if numeric)
value: Value of the sensor if numeric
imprecision: The amount by which the actual measured value may deviate from
'value' due to limitations in the resolution of the given sensor.
"""
def __init__(self, reading, suffix):
self.health = const.Health.Ok
self.type = reading['type']
if 'health' in reading:
self.health = reading['health']
if 'value' in reading:
self.value = reading['value']
else:
self.value = None
self.states = reading['states']
if 'unavailable' in reading:
self.unavailable = 1
self.units = suffix
self.name = reading['name']
self.imprecision = reading['imprecision']
def __repr__(self):
return repr({
'value': self.value,
'states': self.states,
'units': self.units,
'imprecision': self.imprecision,
'name': self.name,
'type': self.type,
'health': self.health
})
def _prettyprint(self):
repr = self.name + ": "
if self.value is not None:
repr += str(self.value)
repr += " ± " + str(self.imprecision)
repr += self.units
for state in self.states:
repr += state + ","
if self.health >= const.Health.Failed:
repr += '(Failed)'
elif self.health >= const.Health.Critical:
repr += '(Critical)'
elif self.health >= const.Health.Warning:
repr += '(Warning)'
return repr
class SDREntry(object):
"""Represent a single entry in the IPMI SDR.
This is created and consumed by pyghmi internally, there is no reason for
external code to pay attention to this class.
"""
def __init__(self, entrybytes, reportunsupported=False):
# ignore record id for now, we only care about the sensor number for
# moment
self.reportunsupported = reportunsupported
if entrybytes[2] != 0x51:
# only recognize '1.5', the only version defined at time of writing
raise NotImplementedError
self.rectype = entrybytes[3]
self.linearization = None
#most important to get going are 1, 2, and 11
self.sdrtype = TYPE_SENSOR # assume a sensor
if self.rectype == 1: # full sdr
self.full_decode(entrybytes[5:])
elif self.rectype == 2: # full sdr
self.compact_decode(entrybytes[5:])
elif self.rectype == 8: # entity association
self.association_decode(entrybytes[5:])
elif self.rectype == 0x11: # FRU locator
self.fru_decode(entrybytes[5:])
elif self.rectype == 0x12: # Management controller
self.mclocate_decode(entrybytes[5:])
elif self.rectype == 0xc0: # OEM format
self.sdrtype = TYPE_UNKNOWN # assume undefined
self.oem_decode(entrybytes[5:])
elif self.reportunsupported:
#will remove once I see it stop being thrown for now
#perhaps need some explicit mode to check for
#unsupported things, but make do otherwise
raise NotImplementedError
else:
self.sdrtype = TYPE_UNKNOWN
@property
def name(self):
if self.sdrtype == TYPE_SENSOR:
return self.sensor_name
elif self.sdrtype == TYPE_FRU:
return self.fru_name
else:
return "UNKNOWN"
def oem_decode(self, entry):
mfgid = entry[0] + (entry[1] << 8) + (entry[2] << 16)
if self.reportunsupported:
raise NotImplementedError("No support for mfgid %X" % mfgid)
def mclocate_decode(self, entry):
# For now, we don't have use for MC locator records
# we'll ignore them at the moment
self.sdrtype = TYPE_UNKNOWN
pass
def fru_decode(self, entry):
# table 43-7 FRU Device Locator
self.sdrtype = TYPE_FRU
self.fru_name = self.tlv_decode(entry[10], entry[11:])
self.fru_number = entry[1]
def association_decode(self, entry):
# table 43-4 Entity Associaition Record
#TODO(jbjohnso): actually represent this data
self.sdrtype = TYPE_UNKNOWN
def compact_decode(self, entry):
# table 43-2 compact sensor record
self._common_decode(entry)
self.sensor_name = self.tlv_decode(entry[26], entry[27:])
def _common_decode(self, entry):
# compact and full are very similar
# this function handles the common aspects of compact and full
# offsets from spec, minus 6
self.sensor_number = entry[2]
self.entity = ipmiconstants.entity_ids[entry[3]]
try:
self.sensor_type = ipmiconstants.sensor_type_codes[entry[7]]
except KeyError:
self.sensor_type = "UNKNOWN type " + str(entry[7])
self.reading_type = entry[8] # table 42-1
# 0: unspecified
# 1: generic threshold based
# 0x6f: discrete sensor-specific from table 42-3, sensor offsets
# all others per table 42-2, generic discrete
self.numeric_format = (entry[15] & 0b11000000) >> 6
# the spec technically reserves numeric_format for compact sensor
# numeric, but common treatment won't break currently
# 0 - unsigned, 1 - 1s complement, 2 - 2s complement, 3 - ignore number
self.sensor_rate = sensor_rates[(entry[15] & 0b111000) >> 3]
self.unit_mod = ""
if (entry[15] & 0b110) == 0b10: # unit1 by unit2
self.unit_mod = "/"
elif (entry[15] & 0b110) == 0b100:
# combine the units by multiplying, SI nomenclature is either spac
# or hyphen, so go with space
self.unit_mod = " "
self.percent = ''
if entry[15] & 1 == 1:
self.percent = '% '
self.baseunit = unit_types[entry[16]]
self.modunit = unit_types[entry[17]]
self.unit_suffix = self.percent + self.baseunit + self.unit_mod + \
self.modunit
def full_decode(self, entry):
#offsets are table from spec, minus 6
#TODO(jbjohnso): table 43-13, put in constants to interpret entry[3]
self._common_decode(entry)
# now must extract the formula data to transform values
# entry[18 to entry[24].
# if not linear, must use get sensor reading factors
# TODO(jbjohnso): the various other values
self.sensor_name = self.tlv_decode(entry[42], entry[43:])
self.linearization = entry[18] & 0b1111111
if self.linearization <= 11:
# the enumuration of linear sensors goes to 11,
# static formula parameters are applicable, decode them
# if 0x70, then the sesor reading will have to get the
# factors on the fly.
# the formula could apply if we bother with nominal
# reading interpretation
self.decode_formula(entry)
def decode_sensor_reading(self, reading):
numeric = None
output = {
'name': self.sensor_name,
'type': self.sensor_type,
}
if reading[1] & 0b100000:
output['unavailable'] = 1
return SensorReading(output, self.unit_suffix)
if self.numeric_format == 2:
numeric = twos_complement(reading[0], 8)
elif self.numeric_format == 1:
numeric = ones_complement(reading[0], 8)
elif self.numeric_format == 0:
numeric = reading[0]
discrete = True
if numeric is not None:
lowerbound = numeric - (0.5 + (self.tolerance / 2.0))
upperbound = numeric + (0.5 + (self.tolerance / 2.0))
lowerbound = self.decode_value(lowerbound)
upperbound = self.decode_value(upperbound)
output['value'] = (lowerbound + upperbound) / 2.0
output['imprecision'] = output['value'] - lowerbound
discrete = False
upper = 'upper'
lower = 'lower'
if self.linearization == 7:
# if the formula is 1/x, then the intuitive sense of upper and
# lower are backwards
upper = 'lower'
lower = 'upper'
output['states'] = []
if not discrete:
output['health'] = const.Health.Ok
if reading[2] & 0b1:
output['health'] |= const.Health.Warning
output['states'].append(lower + " non-critical threshold")
if reading[2] & 0b10:
output['health'] |= const.Health.Critical
output['states'].append(lower + " critical threshold")
if reading[2] & 0b100:
output['health'] |= const.Health.Failed
output['states'].append(lower + " non-recoverable threshold")
if reading[2] & 0b1000:
output['health'] |= const.Health.Warning
output['states'].append(upper + " non-critical threshold")
if reading[2] & 0b10000:
output['health'] |= const.Health.Critical
output['states'].append(upper + " critical threshold")
if reading[2] & 0b100000:
output['health'] |= const.Health.Failed
output['states'].append(upper + " non-recoverable threshold")
return SensorReading(output, self.unit_suffix)
def decode_value(self, value):
# Take the input value and return meaningful value
if self.linearization == 0x70: # direct calling code to get factors
#TODO(jbjohnso): implement get sensor reading factors support for
#non linear sensor
raise NotImplementedError("Need to do get sensor reading factors")
# time to compute the pre-linearization value.
decoded = float((value * self.m + self.b) *
(10 ** self.resultexponent))
if self.linearization == 0:
return decoded
elif self.linearization == 1:
return math.log(decoded)
elif self.linearization == 2:
return math.log(decoded, 10)
elif self.linearization == 3:
return math.log(decoded, 2)
elif self.linearization == 4:
return math.exp(decoded)
elif self.linearization == 5:
return 10 ** decoded
elif self.linearization == 6:
return 2 ** decoded
elif self.linearization == 7:
return 1 / decoded
elif self.linearization == 8:
return decoded ** 2
elif self.linearization == 9:
return decoded ** 3
elif self.linearization == 10:
return math.sqrt(decoded)
elif self.linearization == 11:
return decoded ** (1.0/3)
else:
raise NotImplementedError
def decode_formula(self, entry):
self.m = \
twos_complement(entry[19] + ((entry[20] & 0b11000000) << 2), 10)
self.tolerance = entry[20] & 0b111111
self.b = \
twos_complement(entry[21] + ((entry[22] & 0b11000000) << 2), 10)
self.accuracy = (entry[22] & 0b111111) + \
(entry[23] & 0b11110000) << 2
self.accuracyexp = (entry[23] & 0b1100) >> 2
self.direction = entry[23] & 0b11
#0 = n/a, 1 = input, 2 = output
self.resultexponent = twos_complement((entry[24] & 0b11110000) >> 4, 4)
bexponent = twos_complement(entry[24] & 0b1111, 4)
# might as well do the math to 'b' now rather than wait for later
self.b = self.b * (10**bexponent)
def tlv_decode(self, tlv, data):
# Per IPMI 'type/length byte format
type = (tlv & 0b11000000) >> 6
if not len(data):
return ""
if type == 0: # Unicode per 43.15 in ipmi 2.0 spec
# the spec is not specific about encoding, assuming utf8
return unicode(struct.pack("%dB" % len(data), *data), "utf_8")
elif type == 1: # BCD '+'
tmpl = "%02X" * len(data)
tstr = tmpl % tuple(data)
tstr = tstr.replace("A", " ").replace("B", "-").replace("C", ".")
return tstr.replace("D", ":").replace("E", ",").replace("F", "_")
elif type == 2: # 6 bit ascii, start at 0x20 and stop when out of bits
# the ordering is very peculiar and is best understood from
# IPMI SPEC "6-bit packed ascii example
tstr = ""
while len(data) >= 3: # the packing only works with 3 byte chunks
tstr += chr((data[0] & 0b111111) + 0x20)
tstr += chr(((data[1] & 0b1111) << 2) +
(data[0] >> 6) + 0x20)
tstr += chr(((data[2] & 0b11) << 4) +
(data[1] >> 4) + 0x20)
tstr += chr((data[2] >> 2) + 0x20)
return tstr
elif type == 3: # ACSII+LATIN1
return struct.pack("%dB" % len(data), *data)
class SDR(object):
"""Examine the state of sensors managed by a BMC
Presents the data from sensor read commands as directed by the SDR in a
reasonable format. This module is used by the command module, and is not
intended for consumption by external code directly
:param ipmicmd: A Command class object
"""
def __init__(self, ipmicmd):
self.ipmicmd = ipmicmd
self.sensors = {}
self.fru = {}
self.read_info()
def read_info(self):
#first, we want to know the device id
rsp = self.ipmicmd.raw_command(netfn=6, command=1)
self.device_id = rsp['data'][0]
self.device_rev = rsp['data'][1] & 0b111
# Going to ignore device available until get sdr command
# since that provides usefully distinct state and this does not
self.fw_major = rsp['data'][2] & 0b1111111
self.fw_minor = "%02X" % rsp['data'][3] # BCD encoding, oddly enough
if rsp['data'][1] & 0b10000000:
# For lack of any system with 'device sdrs', raise an
# exception when they are encountered for now, implement or
# ignore later
raise NotImplementedError
self.ipmiversion = rsp['data'][4] # 51h = 1.5, 02h = 2.0
self.mfg_id = rsp['data'][8] << 16 + rsp['data'][7] << 8 + \
rsp['data'][6]
self.prod_id = rsp['data'][10] << 8 + rsp['data'][9]
if len(rsp['data']) > 11:
self.aux_fw = self.decode_aux(rsp['data'][11:15])
self.get_sdr()
def get_sdr(self):
rsp = self.ipmicmd.raw_command(netfn=0x0a, command=0x20)
if (rsp['data'][0] != 0x51):
# we only understand SDR version 51h, the only version defined
# at time of this writing
raise NotImplementedError
#NOTE(jbjohnso): we actually don't need to care about 'numrecords'
# since FFFF marks the end explicitly
#numrecords = (rsp['data'][2] << 8) + rsp['data'][1]
#NOTE(jbjohnso): don't care about 'free space' at the moment
#NOTE(jbjohnso): most recent timstamp data for add and erase could be
# handy to detect cache staleness, but for now will assume invariant
# over life of session
#NOTE(jbjohnso): not looking to support the various options in op
# support, ignore those for now, reservation if some BMCs can't read
# full SDR in one slurp
recid = 0
rsvid = 0 # partial 'get sdr' will require this
offset = 0
size = 0xff
while recid != 0xffff: # per 33.12 Get SDR command, 0xffff marks end
rqdata = [rsvid & 0xff, rsvid >> 8,
recid & 0xff, recid >> 8,
offset, size]
rsp = self.ipmicmd.raw_command(netfn=0x0a, command=0x23,
data=rqdata)
newrecid = (rsp['data'][1] << 8) + rsp['data'][0]
self.add_sdr(rsp['data'][2:])
if newrecid == recid:
raise exc.BmcErrorException("Incorrect SDR record id from BMC")
recid = newrecid
def get_sensor_numbers(self):
return self.sensors.iterkeys()
def add_sdr(self, sdrbytes):
newent = SDREntry(sdrbytes)
if newent.sdrtype == TYPE_SENSOR:
id = newent.sensor_number
if id in self.sensors:
raise exc.BmcErrorException("Duplicate sensor number " + id)
self.sensors[id] = newent
elif newent.sdrtype == TYPE_FRU:
id = newent.fru_number
if id in self.fru:
raise exc.BmcErrorException("Duplicate FRU identifier " + id)
self.fru[id] = newent
def decode_aux(self, auxdata):
# This is where manufacturers can add their own
# decode information
return "".join(hex(x) for x in auxdata)
if __name__ == "__main__": # test code
import os
import sys
password = os.environ['IPMIPASSWORD']
bmc = sys.argv[1]
user = sys.argv[2]
ipmicmd = ipmicmd.Command(bmc=bmc, userid=user, password=password)
sdr = SDR(ipmicmd)
for number in sdr.get_sensor_numbers():
rsp = ipmicmd.raw_command(command=0x2d, netfn=4, data=(number,))
if 'error' in rsp:
continue
reading = sdr.sensors[number].decode_sensor_reading(rsp['data'])
if reading is not None:
print repr(reading)