From 25edd1ac318ae9d3a3ab4e8898cc39b58a3d00a3 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 2 Jun 2015 09:49:02 -0400 Subject: [PATCH] Implement OEM Inventory components Provide a framework for OEM to add inventory devices without requiring FRU locator records. This enables vendors to provide additional data in a more flexible scheme. Change-Id: I9ef9685de9150940321aecbf2cc275cfbbe8ce6a --- pyghmi/ipmi/command.py | 6 +++ pyghmi/ipmi/oem/generic.py | 29 ++++++++++++ pyghmi/ipmi/oem/lenovo.py | 96 +++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/pyghmi/ipmi/command.py b/pyghmi/ipmi/command.py index 4ebf100a..de0ed6e0 100644 --- a/pyghmi/ipmi/command.py +++ b/pyghmi/ipmi/command.py @@ -439,6 +439,9 @@ class Command(object): self._sdr = sdr.SDR(self) for fruid in self._sdr.fru: yield self._sdr.fru[fruid].fru_name + self.oem_init() + for compname in self._oem.get_oem_inventory_descriptions(): + yield compname def get_inventory_of_component(self, component): """Retrieve inventory of a component @@ -455,6 +458,7 @@ class Command(object): if self._sdr.fru[fruid].fru_name == component: return self._oem.process_fru(fru.FRU( ipmicmd=self, fruid=fruid, sdr=self._sdr.fru[fruid]).info) + return self._oem.get_inventory_of_component(component) def _get_zero_fru(self): # It is expected that a manufacturer matches SMBIOS to IPMI @@ -493,6 +497,8 @@ class Command(object): if fruinf is not None: fruinf = self._oem.process_fru(fruinf) yield (self._sdr.fru[fruid].fru_name, fruinf) + for componentpair in self._oem.get_oem_inventory(): + yield componentpair def get_health(self): """Summarize health of managed system diff --git a/pyghmi/ipmi/oem/generic.py b/pyghmi/ipmi/oem/generic.py index 55ee0f03..38334c1c 100644 --- a/pyghmi/ipmi/oem/generic.py +++ b/pyghmi/ipmi/oem/generic.py @@ -41,6 +41,35 @@ class OEMHandler(object): if evdata[0] & 0b110000 == 0b100000: event['oem_byte3'] = evdata[2] + def get_oem_inventory_descriptions(self): + """Get descriptions of available additional inventory items + + OEM implementation may provide additional records not indicated + by FRU locator SDR records. An implementation is expected to + implement this function to list component names that would map to + OEM behavior beyond the specification. It should return an iterable + of names + """ + return () + + def get_oem_inventory(self): + """Get tuples of component names and inventory data. + + This returns an iterable of tuples. The first member of each tuple + is a string description of the inventory item. The second member + is a dict of inventory information about the component. + """ + for desc in self.get_oem_inventory_descriptions(): + yield (desc, self.get_inventory_of_component(desc)) + + def get_inventory_of_component(self, component): + """Get inventory detail of an OEM defined component + + Given a string that may be an OEM component, return the detail of that + component. If the component does not exist, returns None + """ + return None + def process_fru(self, fru): """Modify a fru entry with OEM understanding. diff --git a/pyghmi/ipmi/oem/lenovo.py b/pyghmi/ipmi/oem/lenovo.py index 38754d8d..15b840f4 100644 --- a/pyghmi/ipmi/oem/lenovo.py +++ b/pyghmi/ipmi/oem/lenovo.py @@ -17,7 +17,9 @@ import pyghmi.constants as pygconst import pyghmi.ipmi.oem.generic as generic import pyghmi.ipmi.private.constants as ipmiconst +import pyghmi.ipmi.private.spd as spd import pyghmi.ipmi.private.util as util +import struct firmware_types = { 1: 'Management Controller', @@ -67,6 +69,8 @@ class OEMHandler(generic.OEMHandler): # will need to retain data to differentiate # variations. For example System X versus Thinkserver self.oemid = oemid + self.ipmicmd = ipmicmd + self.oem_inventory_info = None def process_event(self, event, ipmicmd, seldata): if 'oemdata' in event: @@ -143,11 +147,99 @@ class OEMHandler(generic.OEMHandler): event['component_type_id'] == 13): event['component'] += ' {0}'.format(evdata[1] & 0b11111) + @property + def has_tsm(self): + """True if this particular server have a TSM based service processor + """ + return (self.oemid['manufacturer_id'] == 19046 and + self.oemid['device_id'] == 32) + + def get_oem_inventory_descriptions(self): + if self.has_tsm: + # Thinkserver with TSM + if not self.oem_inventory_info: + self._collect_tsm_inventory() + return iter(self.oem_inventory_info) + return () + + def get_oem_inventory(self): + if self.has_tsm: + self._collect_tsm_inventory() + for compname in self.oem_inventory_info: + yield (compname, self.oem_inventory_info[compname]) + + def get_inventory_of_component(self, component): + if self.has_tsm: + self._collect_tsm_inventory() + return self.oem_inventory_info.get(component, None) + + def _decode_tsm_cpu(self, offset, cpudata): + keytext = 'CPU {0}'.format(ord(cpudata[offset])) + self.oem_inventory_info[keytext] = {} + if cpudata[offset + 1] == '\x00': + self.oem_inventory_info[keytext] = None + return + self.oem_inventory_info[keytext]['cores'] = ord( + cpudata[offset + 1]) + self.oem_inventory_info[keytext]['threads'] = ord( + cpudata[offset + 2]) + self.oem_inventory_info[keytext]['manufacturer'] = \ + cpudata[offset + 3:offset + 16].rstrip('\x00') + self.oem_inventory_info[keytext]['family'] = \ + cpudata[offset + 16: offset + 46].rstrip('\x00') + self.oem_inventory_info[keytext]['model'] = \ + cpudata[offset + 46: offset + 76].rstrip('\x00') + self.oem_inventory_info[keytext]['stepping'] = \ + cpudata[offset + 76: offset + 79].rstrip('\x00') + self.oem_inventory_info[keytext]['frequency'] = \ + '{0:.1f} GHz'.format( + struct.unpack('I', memdata[offset + 57:offset + 61])[0] + self.oem_inventory_info[keytext]['model'] = \ + memdata[offset + 61:offset + 82].rstrip('\x00') + + def _collect_tsm_inventory(self): + # Collect CPU inventory + self.oem_inventory_info = {} + rsp = self.ipmicmd.xraw_command(netfn=6, command=0x59, + data=(0, 0xc1, 1, 0)) + compcount = ord(rsp['data'][1]) + for cpu in xrange(0, compcount): + offset = 2 + (85 * cpu) + self._decode_tsm_cpu(offset, rsp['data']) + # Collect memory inventory + rsp = self.ipmicmd.xraw_command(netfn=6, command=0x59, + data=(0, 0xc1, 2, 0)) + compcount = ord(rsp['data'][1]) + for dimm in xrange(0, compcount): + offset = 2 + (dimm * 84) + self._decode_tsm_dimm(offset, rsp['data']) + def process_fru(self, fru): if fru is None: return fru - if (self.oemid['manufacturer_id'] == 19046 and - self.oemid['device_id'] == 32): + if self.has_tsm: fru['oem_parser'] = 'lenovo' # Thinkserver lays out specific interpretation of the # board extra fields