Mini Shell
# 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:])
Zerion Mini Shell 1.0