# Copyright (c) 2014 Kontron Europe GmbH # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import array import codecs import datetime import os from .errors import DecodingError, CompletionCodeError from .msgs import constants from .utils import bcd_search, chunks, py3_array_tobytes from .fields import FruTypeLengthString codecs.register(bcd_search) class Fru(object): def __init__(self): self.write_length = 16 def get_fru_inventory_area_info(self, fru_id=0): rsp = self.send_message_with_name('GetFruInventoryAreaInfo', fru_id=fru_id) return rsp.area_size def write_fru_data(self, data, offset=0, fru_id=0): for chunk in chunks(data, self.write_length): write_rsp = self.send_message_with_name('WriteFruData', fru_id=fru_id, offset=offset, data=chunk) # check if device wrote the same number of bytes sent if write_rsp.count_written != len(chunk): raise Exception('sent {:} bytes but device wrote {:} bytes' .format(len(chunk), write_rsp.count_written)) offset += len(chunk) def read_fru_data(self, offset=None, count=None, fru_id=0): req_size = 32 data = array.array('B') # first check for maximum area size if offset is None: area_size = self.get_fru_inventory_area_info(fru_id) off = 0 else: area_size = offset + count off = offset while off < area_size: if (off + req_size) > area_size: req_size = area_size - off try: rsp = self.send_message_with_name('ReadFruData', fru_id=fru_id, offset=off, count=req_size) except CompletionCodeError as ex: if ex.cc in (constants.CC_CANT_RET_NUM_REQ_BYTES, constants.CC_REQ_DATA_FIELD_EXCEED, constants.CC_PARAM_OUT_OF_RANGE): req_size -= 2 if req_size <= 0: raise continue else: raise data.extend(rsp.data) off += rsp.count return py3_array_tobytes(data) def read_fru_data_full(self, fru_id=0): return self.read_fru_data(fru_id=fru_id) def get_fru_inventory_header(self, fru_id=0): data = self.read_fru_data(offset=0, count=8, fru_id=fru_id) return InventoryCommonHeader(data) def _read_fru_area(self, offset, fru_id=0): # read the area header data = self.read_fru_data(offset=offset, count=5, fru_id=fru_id) # get the whole area data count = data[1] * 8 return self.read_fru_data(offset=offset, count=count, fru_id=fru_id) def get_fru_chassis_area(self, fru_id=0): header = self.get_fru_inventory_header(fru_id=fru_id) data = self._read_fru_area(offset=header.chassis_info_area_offset, fru_id=fru_id) return InventoryChassisInfoArea(data) def get_fru_board_area(self, fru_id=0): header = self.get_fru_inventory_header(fru_id=fru_id) data = self._read_fru_area(offset=header.board_info_area_offset, fru_id=fru_id) return InventoryBoardInfoArea(data) def get_fru_product_area(self, fru_id=0): header = self.get_fru_inventory_header(fru_id=fru_id) data = self._read_fru_area(offset=header.product_info_area_offset, fru_id=fru_id) return InventoryProductInfoArea(data) def get_fru_multirecord_area(self, fru_id=0): header = self.get_fru_inventory_header(fru_id=fru_id) # we have to determine the length of the area first offset = header.multirecord_area_offset count = 0 while True: # read the header data = self.read_fru_data(offset=offset, count=5) end_of_list = bool(data[1] & 0x80) length = data[2] count += length + 5 offset += length + 5 if end_of_list: break # now read the full area offset = header.multirecord_area_offset data = self.read_fru_data(offset=offset, count=count) return InventoryMultiRecordArea(data) def get_fru_inventory(self, fru_id=0): """ Get the full parsed FRU inventory data. """ fru = FruInventory() header = self.get_fru_inventory_header(fru_id=fru_id) if header.chassis_info_area_offset: fru.chassis_info_area = self.get_fru_chassis_area(fru_id=fru_id) if header.board_info_area_offset: fru.board_info_area = self.get_fru_board_area(fru_id=fru_id) if header.product_info_area_offset: fru.product_info_area = self.get_fru_product_area(fru_id=fru_id) if header.multirecord_area_offset: fru.multirecord_area = self.get_fru_multirecord_area(fru_id=fru_id) return fru def get_fru_inventory_from_file(filename): try: file = open(filename, "rb") except IOError: print('Error open file "%s"' % filename) ################################ # get file size file_size = os.stat(filename).st_size file_data = file.read(file_size) data = array.array('B', file_data) file.close() return FruInventory(data) CUSTOM_FIELD_END = 0xc1 def _decode_custom_fields(data): offset = 0 fields = [] while data[offset] != CUSTOM_FIELD_END: field = FruTypeLengthString(data, offset) fields.append(field) offset += field.length + 1 return fields class FruData(object): def __init__(self, data=None): if data: if isinstance(data, str): data = [ord(c) for c in data] self.data = data if hasattr(self, '_from_data'): self._from_data(data) class InventoryCommonHeader(FruData): def _from_data(self, data): if len(data) != 8: raise DecodingError('InventoryCommonHeader length != 8') self.format_version = data[0] & 0x0f self.internal_use_area_offset = data[1] * 8 or None self.chassis_info_area_offset = data[2] * 8 or None self.board_info_area_offset = data[3] * 8 or None self.product_info_area_offset = data[4] * 8 or None self.multirecord_area_offset = data[5] * 8 or None if sum(data) % 256 != 0: raise DecodingError('InventoryCommonHeader checksum failed') class CommonInfoArea(FruData): def _from_data(self, data): self.format_version = data[0] & 0x0f if self.format_version != 1: raise DecodingError('unsupported format version (%d)' % self.format_version) self.length = data[1] * 8 if sum(data[:self.length]) % 256 != 0: raise DecodingError('checksum failed') class InventoryChassisInfoArea(CommonInfoArea): TYPE_OTHER = 1 TYPE_UNKNOWN = 2 TYPE_DESKTOP = 3 TYPE_LOW_PROFILE_DESKTOP = 4 TYPE_PIZZA_BOX = 5 TYPE_MINI_TOWER = 6 TYPE_TOWER = 7 TYPE_PORTABLE = 8 TYPE_LAPTOP = 9 TYPE_NOTEBOOK = 10 TYPE_HAND_HELD = 11 TYPE_DOCKING_STATION = 12 TYPE_ALL_IN_ONE = 13 TYPE_SUB_NOTEBOOK = 14 TYPE_SPACE_SAVING = 15 TYPE_LUNCH_BOX = 16 TYPE_MAIN_SERVER_CHASSIS = 17 TYPE_EXPANSION_CHASSIS = 18 TYPE_SUB_CHASSIS = 19 TYPE_BUS_EXPANSION_CHASSIS = 20 TYPE_PERIPHERAL_CHASSIS = 21 TYPE_RAID_CHASSIS = 22 TYPE_RACK_MOUNT_CHASSIS = 23 def _from_data(self, data): CommonInfoArea._from_data(self, data) self.type = data[2] offset = 3 self.part_number = FruTypeLengthString(data, offset) offset += self.part_number.length + 1 self.serial_number = FruTypeLengthString(data, offset, True) offset += self.serial_number.length + 1 self.custom_chassis_info = _decode_custom_fields(data[offset:]) class InventoryBoardInfoArea(CommonInfoArea): def _from_data(self, data): CommonInfoArea._from_data(self, data) self.language_code = data[2] minutes = data[5] << 16 | data[4] << 8 | data[3] self.mfg_date = (datetime.datetime(1996, 1, 1) + datetime.timedelta(minutes=minutes)) offset = 6 self.manufacturer = FruTypeLengthString(data, offset) offset += self.manufacturer.length + 1 self.product_name = FruTypeLengthString(data, offset) offset += self.product_name.length + 1 self.serial_number = FruTypeLengthString(data, offset, True) offset += self.serial_number.length + 1 self.part_number = FruTypeLengthString(data, offset) offset += self.part_number.length + 1 self.fru_file_id = FruTypeLengthString(data, offset, True) offset += self.fru_file_id.length + 1 self.custom_mfg_info = _decode_custom_fields(data[offset:]) class InventoryProductInfoArea(CommonInfoArea): def _from_data(self, data): CommonInfoArea._from_data(self, data) self.language_code = data[2] offset = 3 self.manufacturer = FruTypeLengthString(data, offset) offset += self.manufacturer.length + 1 self.name = FruTypeLengthString(data, offset) offset += self.name.length + 1 self.part_number = FruTypeLengthString(data, offset) offset += self.part_number.length + 1 self.version = FruTypeLengthString(data, offset) offset += self.version.length + 1 self.serial_number = FruTypeLengthString(data, offset, True) offset += self.serial_number.length + 1 self.asset_tag = FruTypeLengthString(data, offset) offset += self.asset_tag.length + 1 self.fru_file_id = FruTypeLengthString(data, offset, True) offset += self.fru_file_id.length + 1 self.custom_mfg_info = list() self.custom_mfg_info = _decode_custom_fields(data[offset:]) class FruDataMultiRecord(FruData): TYPE_POWER_SUPPLY_INFORMATION = 0 TYPE_DC_OUTPUT = 1 TYPE_DC_LOAD = 2 TYPE_MANAGEMENT_ACCESS_RECORD = 3 TYPE_BASE_COMPATIBILITY_RECORD = 4 TYPE_EXTENDED_COMPATIBILITY_RECORD = 5 TYPE_OEM = list(range(0x0c, 0x100)) TYPE_OEM_PICMG = 0xc0 def __str__(self): return '%02x: %s' % (self.record_type_id, ' '.join('%02x' % b for b in self.raw)) def _from_data(self, data): if len(data) < 5: raise DecodingError('data too short') self.record_type_id = data[0] self.format_version = data[1] & 0x0f self.end_of_list = bool(data[1] & 0x80) self.length = data[2] if sum(data[:5]) % 256 != 0: raise DecodingError('FruDataMultiRecord header checksum failed') self.raw = data[5:5+self.length] if (sum(self.raw) + data[3]) % 256 != 0: raise DecodingError('FruDataMultiRecord record checksum failed') @staticmethod def create_from_record_id(data): if data[0] == FruDataMultiRecord.TYPE_OEM_PICMG: return FruPicmgRecord.create_from_record_id(data) else: return FruDataUnknown(data) class FruDataUnknown(FruDataMultiRecord): """This class is used to indicate undecoded picmg record.""" pass class FruPicmgRecord(FruDataMultiRecord): PICMG_RECORD_ID_BACKPLANE_PTP_CONNECTIVITY = 0x04 PICMG_RECORD_ID_ADDRESS_TABLE = 0x10 PICMG_RECORD_ID_SHELF_POWER_DISTRIBUTION = 0x11 PICMG_RECORD_ID_SHMC_ACTIVATION_MANAGEMENT = 0x12 PICMG_RECORD_ID_SHMC_IP_CONNECTION = 0x13 PICMG_RECORD_ID_BOARD_PTP_CONNECTIVITY = 0x14 PICMG_RECORD_ID_RADIAL_IPMB0_LINK_MAPPING = 0x15 PICMG_RECORD_ID_MODULE_CURRENT_REQUIREMENTS = 0x16 PICMG_RECORD_ID_CARRIER_ACTIVATION_MANAGEMENT = 0x17 PICMG_RECORD_ID_CARRIER_PTP_CONNECTIVITY = 0x18 PICMG_RECORD_ID_AMC_PTP_CONNECTIVITY = 0x19 PICMG_RECORD_ID_CARRIER_INFORMATION = 0x1a PICMG_RECORD_ID_MTCA_FRU_INFORMATION_PARTITION = 0x20 PICMG_RECORD_ID_MTCA_CARRIER_MANAGER_IP_LINK = 0x21 PICMG_RECORD_ID_MTCA_CARRIER_INFORMATION = 0x22 PICMG_RECORD_ID_MTCA_SHELF_INFORMATION = 0x23 PICMG_RECORD_ID_MTCA_SHELF_MANAGER_IP_LINK = 0x24 PICMG_RECORD_ID_MTCA_CARRIER_POWER_POLICY = 0x25 PICMG_RECORD_ID_MTCA_CARRIER_ACTIVATION_AND_POWER = 0x26 PICMG_RECORD_ID_MTCA_POWER_MODULE_CAPABILITY = 0x27 PICMG_RECORD_ID_MTCA_FAN_GEOGRAPHY = 0x28 PICMG_RECORD_ID_OEM_MODULE_DESCRIPTION = 0x29 PICMG_RECORD_ID_CARRIER_CLOCK_PTP_CONNECTIVITY = 0x2C PICMG_RECORD_ID_CLOCK_CONFIGURATION = 0x2d PICMG_RECORD_ID_ZONE_3_INTERFACE_COMPATIBILITY = 0x30 PICMG_RECORD_ID_CARRIER_BUSED_CONNECTIVITY = 0x31 PICMG_RECORD_ID_ZONE_3_INTERFACE_DOCUMENTATION = 0x32 def __init__(self, data): FruDataMultiRecord.__init__(self, data) @staticmethod def create_from_record_id(data): picmg_record = FruPicmgRecord(data) if picmg_record.picmg_record_type_id ==\ FruPicmgRecord.PICMG_RECORD_ID_MTCA_POWER_MODULE_CAPABILITY: return FruPicmgPowerModuleCapabilityRecord(data) return FruPicmgRecord(data) def _from_data(self, data): if len(data) < 10: raise DecodingError('data too short') data = array.array('B', data) FruDataMultiRecord._from_data(self, data) self.manufacturer_id = \ data[5] | data[6] << 8 | data[7] << 16 self.picmg_record_type_id = data[8] self.format_version = data[9] class FruPicmgPowerModuleCapabilityRecord(FruPicmgRecord): def _from_data(self, data): if len(data) < 12: raise DecodingError('data too short') FruPicmgRecord._from_data(self, data) maximum_current_output = data[10] | data[11] << 8 self.maximum_current_output = float(maximum_current_output/10) class InventoryMultiRecordArea(object): def __init__(self, data): if data: self._from_data(data) def _from_data(self, data): self.records = list() offset = 0 while True: record = FruDataMultiRecord.create_from_record_id(data[offset:]) self.records.append(record) offset += record.length + 5 if record.end_of_list: break class FruInventory(object): def __init__(self, data=None): self.chassis_info_area = None self.board_info_area = None self.product_info_area = None self.multirecord_area = None if data: self._from_data(data) def _from_data(self, data): self.raw = data self.common_header = InventoryCommonHeader(data[:8]) if self.common_header.chassis_info_area_offset: self.chassis_info_area = InventoryChassisInfoArea( data[self.common_header.chassis_info_area_offset:]) if self.common_header.board_info_area_offset: self.board_info_area = InventoryBoardInfoArea( data[self.common_header.board_info_area_offset:]) if self.common_header.product_info_area_offset: self.product_info_area = InventoryProductInfoArea( data[self.common_header.product_info_area_offset:]) if self.common_header.multirecord_area_offset: self.multirecord_area = InventoryMultiRecordArea( data[self.common_header.multirecord_area_offset:])