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
from __future__ import print_function
import os
import codecs
import struct
import collections
import hashlib
import time
from array import array
from .errors import CompletionCodeError, HpmError, IpmiTimeoutError
from .msgs import create_request_by_name
from .msgs import constants
from .utils import check_completion_code, bcd_search, chunks
from .utils import py3dec_unic_bytes_fix, py3_array_tobytes
from .state import State
from .fields import VersionField
PROPERTY_GENERAL_PROPERTIES = 0
PROPERTY_CURRENT_VERSION = 1
PROPERTY_DESCRIPTION_STRING = 2
PROPERTY_ROLLBACK_VERSION = 3
PROPERTY_DEFERRED_VERSION = 4
PROPERTY_OEM = list(range(192, 255))
ACTION_BACKUP_COMPONENT = 0x00
ACTION_PREPARE_COMPONENT = 0x01
ACTION_UPLOAD_FOR_UPGRADE = 0x02
ACTION_UPLOAD_FOR_COMPARE = 0x03
CC_LONG_DURATION_CMD_IN_PROGRESS = 0x80
CC_GET_COMP_PROP_UPGRADE_NOT_SUPPORTED_OVER_INTF = 0x81
CC_GET_COMP_PROP_INVALID_COMPONENT = 0x82
CC_GET_COMP_PROP_INVALID_PROPERTIES_SELECTOR = 0x83
CC_INITIATE_UPGRADE_CMD_IN_PROGRESS = 0x80
CC_INITIATE_UPGRADE_INVALID_COMPONENT = 0x81
CC_QUERY_SELFTEST_COMPLETED = 0x00
CC_QUERY_SELFTEST_IN_PROGRESS = 0x80
CC_QUERY_SELFTEST_UPGRADE_NOT_SUPPORTED_OVER_INTF = 0x81
CC_QUERY_SELFTEST_NO_RESULTS_AVAILABLE = 0xD5
CC_ABORT_UPGRADE_CANNOT_ABORT = 0x80
CC_ABORT_UPGRADE_CANNOT_RESUME_OPERATION = 0x81
class Hpm(object):
@staticmethod
def _get_component_count(components):
"""Return the number of components."""
return bin(components).count('1')
def get_target_upgrade_capabilities(self):
rsp = self.send_message_with_name('GetTargetUpgradeCapabilities')
return TargetUpgradeCapabilities(rsp)
def get_component_property(self, component_id, property_id):
rsp = self.send_message_with_name('GetComponentProperties',
id=component_id,
selector=property_id)
return ComponentProperty.from_data(property_id, rsp.data)
def get_component_properties(self, component_id):
properties = []
for p in (PROPERTY_GENERAL_PROPERTIES, PROPERTY_CURRENT_VERSION,
PROPERTY_DESCRIPTION_STRING, PROPERTY_ROLLBACK_VERSION,
PROPERTY_DEFERRED_VERSION):
try:
prop = self.get_component_property(component_id, p)
if prop is not None:
properties.append(prop)
except CompletionCodeError as e:
if e.cc == CC_GET_COMP_PROP_INVALID_PROPERTIES_SELECTOR:
continue
return properties
def find_component_id_by_descriptor(self, descriptor):
caps = self.get_target_upgrade_capabilities()
for component_id in caps.components:
prop = self.get_component_property(component_id,
PROPERTY_DESCRIPTION_STRING)
if prop is not None:
if prop.description == descriptor:
return component_id
return None
def abort_firmware_upgrade(self):
self.send_message_with_name('AbortFirmwareUpgrade')
def initiate_upgrade_action(self, components_mask, action):
"""Initiate Upgrade Action.
components:
action:
ACTION_BACKUP_COMPONENT = 0x00
ACTION_PREPARE_COMPONENT = 0x01
ACTION_UPLOAD_FOR_UPGRADE = 0x02
ACTION_UPLOAD_FOR_COMPARE = 0x03
"""
if action in (ACTION_UPLOAD_FOR_UPGRADE, ACTION_UPLOAD_FOR_COMPARE):
if self._get_component_count(components_mask) != 1:
raise HpmError("more than 1 component not support for action")
self.send_message_with_name('InitiateUpgradeAction',
components=components_mask, action=action)
def initiate_upgrade_action_and_wait(self, components_mask, action,
timeout=2, interval=0.1):
"""Initiate Upgrade Action and wait for long running command."""
try:
self.initiate_upgrade_action(components_mask, action)
except CompletionCodeError as e:
if e.cc == CC_LONG_DURATION_CMD_IN_PROGRESS:
self.wait_for_long_duration_command(
constants.CMDID_HPM_INITIATE_UPGRADE_ACTION,
timeout, interval)
else:
raise HpmError('initiate_upgrade_action CC=0x%02x' % e.cc)
def upload_firmware_block(self, block_number, data):
if isinstance(data, str):
data = [ord(c) for c in data]
self.send_message_with_name('UploadFirmwareBlock', number=block_number,
data=data)
@staticmethod
def _determine_max_block_size():
return 22
def upload_binary(self, binary, timeout=2, interval=0.1, retry=3):
"""Upload all firmware blocks from a binary."""
block_number = 0
block_size = self._determine_max_block_size()
for chunk in chunks(binary, block_size):
try:
self.upload_firmware_block(block_number, chunk)
except CompletionCodeError as e:
if e.cc == CC_LONG_DURATION_CMD_IN_PROGRESS:
self.wait_for_long_duration_command(
constants.CMDID_HPM_UPLOAD_FIRMWARE_BLOCK,
timeout, interval)
else:
raise HpmError('upload_firmware_block CC=0x%02x' % e.cc)
except IpmiTimeoutError:
retry -= 1
if retry == 0:
raise IpmiTimeoutError()
block_number += 1
block_number &= 0xff
def finish_firmware_upload(self, component, length):
return self.send_message_with_name('FinishFirmwareUpload',
component_id=component,
image_length=length)
def finish_upload_and_wait(self, component, length,
timeout=2, interval=0.1):
"""Finish, upload and for the firmware."""
try:
rsp = self.finish_firmware_upload(component, length)
check_completion_code(rsp.completion_code)
except CompletionCodeError as e:
if e.cc == CC_LONG_DURATION_CMD_IN_PROGRESS:
self.wait_for_long_duration_command(
constants.CMDID_HPM_FINISH_FIRMWARE_UPLOAD,
timeout, interval)
else:
raise HpmError('finish_firmware_upload CC=0x%02x' % e.cc)
def get_upgrade_status(self):
return UpgradeStatus(self.send_message_with_name('GetUpgradeStatus'))
def wait_for_long_duration_command(self, expected_cmd, timeout, interval):
start_time = time.time()
while time.time() < start_time + timeout:
try:
status = self.get_upgrade_status()
if status.command_in_progress is not expected_cmd \
and status.command_in_progress != 0x34:
pass
if status.last_completion_code \
== CC_LONG_DURATION_CMD_IN_PROGRESS:
time.sleep(interval)
else:
return
except IpmiTimeoutError:
time.sleep(interval)
except IOError:
time.sleep(interval)
def activate_firmware(self, rollback_override=None):
req = create_request_by_name('ActivateFirmware')
if rollback_override is not None:
req.rollback_override_policy = rollback_override
rsp = self.send_message(req)
check_completion_code(rsp.completion_code)
def activate_firmware_and_wait(self, rollback_override=None,
timeout=2, interval=1):
"""Activate and wait for the new uploaded firmware."""
try:
self.activate_firmware(rollback_override)
except CompletionCodeError as e:
if e.cc == CC_LONG_DURATION_CMD_IN_PROGRESS:
self.wait_for_long_duration_command(
constants.CMDID_HPM_ACTIVATE_FIRMWARE,
timeout, interval)
else:
raise HpmError('activate_firmware CC=0x%02x' % e.cc)
except IpmiTimeoutError:
# controller is in reset and flashed new firmware
pass
def query_selftest_results(self):
return SelfTestResult(
self.send_message_with_name('QuerySelftestResults'))
def query_rollback_status(self):
return RollbackStatus(
self.send_message_with_name('QueryRollbackStatus'))
def initiate_manual_rollback(self):
return RollbackStatus(
self.send_message_with_name('InitiateManualRollback'))
def initiate_manual_rollback_and_wait(self, timeout=2, interval=0.1):
try:
self.initiate_manual_rollback()
except CompletionCodeError as e:
if e.cc == CC_LONG_DURATION_CMD_IN_PROGRESS:
self.wait_for_long_duration_command(
constants.CMDID_HPM_INITIATE_MANUAL_ROLLBACK,
60, interval)
else:
raise HpmError('activate_firmware CC=0x%02x' % e.cc)
except IpmiTimeoutError:
# controller is in reset and flashed new firmware
pass
@staticmethod
def open_upgrade_image(filename):
return UpgradeImage(filename)
@staticmethod
def get_upgrade_version_from_file(filename):
image = UpgradeImage(filename)
for action in image.actions:
if isinstance(action, UpgradeActionRecordUploadForUpgrade):
return action.firmware_version
return None
@staticmethod
def _do_upgrade_action_backup(image):
for action in image.actions:
if isinstance(action, UpgradeActionRecordBackup):
pass
@staticmethod
def _do_upgrade_action_prepare(image):
for action in image.actions:
if isinstance(action, UpgradeActionRecordPrepare):
print("do ACTION_PREPARE_COMPONENT")
@staticmethod
def _do_upgrade_action_upload(image):
for action in image.actions:
if isinstance(action, UpgradeActionRecordUploadForUpgrade):
print("do ACTION_UPLOAD_FOR_UPGRADE")
def preparation_stage(self, image):
####################################################
# match device ID, manfuacturer ID, etc.
device_id = self.get_device_id()
header = image.header
if header.device_id != device_id.device_id:
raise HpmError('Device ID: image=0x%x device=0x%x'
% (header.device_id, device_id.device_id))
if header.manufacturer_id != device_id.manufacturer_id:
raise HpmError('Manufacturer ID: image=0x%x device=0x%x'
% (header.manufacturer_id,
device_id.manufacturer_id))
if header.product_id != device_id.product_id:
raise HpmError('Product ID: image=0x%x device=0x%x'
% (header.product_id, device_id.product_id))
# tbd check version
####################################################
# compare current revision with upgrade image earlist comp rev
targetCap = self.get_target_upgrade_capabilities()
# tbd check version
####################################################
# Match IPM Controller capabilities with Upgrade Image capabilities
support = False
for imageComponent in header.components:
if imageComponent in targetCap.components:
support = True
if support is not True:
raise HpmError('no supported component in image')
def upgrade_stage(self, image, component):
for action in image.actions:
if action.components & (1 << component) == 0:
continue
self.initiate_upgrade_action_and_wait(1 << component,
action.action_type)
if isinstance(action, UpgradeActionRecordUploadForUpgrade):
self.upload_binary(action.firmware_image_data)
self.finish_upload_and_wait(component, action.firmware_length)
def _activation_state_do_self_testing(self):
pass
def wait_until_new_firmware_comes_up(self, timeout, interval):
start_time = time.time()
while time.time() < start_time + timeout:
try:
self.get_upgrade_status()
self.get_device_id()
except IpmiTimeoutError:
time.sleep(interval)
except IOError:
time.sleep(interval)
time.sleep(5)
def activation_stage(self, image, component):
self.activate_firmware_and_wait(
image.header.inaccessibility_timeout, 1)
self.wait_until_new_firmware_comes_up(
image.header.inaccessibility_timeout, 1)
self._activation_state_do_self_testing()
def install_component_from_image(self, image, component):
self.abort_firmware_upgrade()
if component not in image.header.components:
raise HpmError('component=%d not in image' % component)
self.preparation_stage(image)
self.upgrade_stage(image, component)
self.activation_stage(image, component)
def install_component_from_file(self, filename, component):
image = UpgradeImage(filename)
self.install_component_from_image(image, component)
class UpgradeStatus(State):
def _from_response(self, rsp):
self.command_in_progress = rsp.command_in_progress
self.last_completion_code = rsp.last_completion_code
def __str__(self):
string = []
string.append("cmd=0x%02x cc=0x%02x" %
(self.command_in_progress, self.last_completion_code))
return "\n".join(string)
class TargetUpgradeCapabilities(State):
def _from_response(self, rsp):
self.version = rsp.hpm_1_version
self.components = []
for i in range(8):
if rsp.component_present & (1 << i):
self.components.append(i)
def __str__(self):
string = []
string.append("Target Upgrade Capabilities")
string.append(" HPM.1 version: %s" % self.version)
string.append(" Components: %s" % self.components)
return "\n".join(string)
codecs.register(bcd_search)
class ComponentProperty(object):
def __init__(self, data=None):
if (data):
self._from_rsp_data(data)
@staticmethod
def from_data(component_id, data):
if isinstance(data, str):
data = [ord(c) for c in data]
if component_id is PROPERTY_GENERAL_PROPERTIES:
return ComponentPropertyGeneral(data)
elif component_id is PROPERTY_CURRENT_VERSION:
return ComponentPropertyCurrentVersion(data)
elif component_id is PROPERTY_DESCRIPTION_STRING:
return ComponentPropertyDescriptionString(data)
elif component_id is PROPERTY_ROLLBACK_VERSION:
return ComponentPropertyRollbackVersion(data)
elif component_id is PROPERTY_DEFERRED_VERSION:
return ComponentPropertyDeferredVersion(data)
elif component_id in PROPERTY_OEM:
raise NotImplementedError
class ComponentPropertyGeneral(ComponentProperty):
ROLLBACK_SUPPORT_MASK = 0x03
PREPARATION_SUPPORT_MASK = 0x04
COMPARISON_SUPPORT_MASK = 0x08
DEFERRED_ACTIVATION_SUPPORT_MASK = 0x10
PAYLOAD_COLD_RESET_REQ_SUPPORT_MASK = 0x20
def _from_rsp_data(self, data):
support = []
cap = data[0]
if cap & self.ROLLBACK_SUPPORT_MASK == 0:
support.append('rollback_backup_not_supported')
elif cap & self.ROLLBACK_SUPPORT_MASK == 1:
support.append('rollback_is_supported')
elif cap & self.ROLLBACK_SUPPORT_MASK == 2:
support.append('rollback_is_supported')
elif cap & self.ROLLBACK_SUPPORT_MASK == 3:
support.append('reserved')
if cap & self.PREPARATION_SUPPORT_MASK:
support.append('prepartion')
if cap & self.COMPARISON_SUPPORT_MASK:
support.append('comparison')
if cap & self.DEFERRED_ACTIVATION_SUPPORT_MASK:
support.append('deferred_activation')
if cap & self.PAYLOAD_COLD_RESET_REQ_SUPPORT_MASK:
support.append('payload_cold_reset_required')
self.general = support
class ComponentPropertyCurrentVersion(ComponentProperty):
def _from_rsp_data(self, data):
self.version = VersionField(data)
class ComponentPropertyDescriptionString(ComponentProperty):
def _from_rsp_data(self, data):
descr = py3_array_tobytes(array('B', data))
descr = py3dec_unic_bytes_fix(descr)
# strip '\x00'
descr = descr.replace('\0', '')
self.description = descr
class ComponentPropertyRollbackVersion(ComponentProperty):
def _from_rsp_data(self, data):
self.version = VersionField(data)
class ComponentPropertyDeferredVersion(ComponentProperty):
def _from_rsp_data(self, data):
self.version = VersionField(data)
class ComponentPropertyOem(ComponentProperty):
def _from_rsp_data(self, data):
self.oem_data = data
class SelfTestResult(State):
CORRUPTED_OR_INACCESSIBLE_DATA_OR_DEVICES = 0x57
def _from_response(self, rsp):
self.status = rsp.selftest_result_1
result2 = rsp.selftest_result_2
if self.status != self.CORRUPTED_OR_INACCESSIBLE_DATA_OR_DEVICES:
self.fail_sel = (result2 & 0x80) >> 7
self.fail_sdrr = (result2 & 0x40) >> 6
self.fail_bmc_fru = (result2 & 0x20) >> 5
self.fail_ipmb = (result2 & 0x10) >> 4
self.fail_sdrr_empty = (result2 & 0x08) >> 3
self.fail_bmc_fru_interanl_area = (result2 & 0x04) >> 2
self.fail_bootblock = (result2 & 0x02) >> 1
self.fail_mc = (result2 & 0x01) >> 0
class RollbackStatus(object):
def __init__(self, rsp=None):
if rsp:
self._from_rsp(rsp)
def _from_rsp(self, rsp):
if rsp.completion_estimate:
self.percent_complete = rsp.completion_estimate
image_header = collections.namedtuple('image_header',
['field_name', 'format', 'start', 'len'])
class UpgradeImageHeaderRecord(object):
FORMAT = [
image_header('format_version', 'B', 8, 1),
image_header('device_id', 'B', 9, 1),
image_header('product_id', '<H', 13, 2),
image_header('time', '<L', 15, 4),
image_header('capabilities', 'B', 19, 1),
image_header('selftest_timeout', 'B', 21, 1),
image_header('rollback_timeout', 'B', 22, 1),
image_header('inaccessibility_timeout', 'B', 23, 1),
image_header('earliest_compatible_revision', '<H', 24, 2),
image_header('oem_data_length', '<H', 32, 2),
]
def __init__(self, data=None):
for a in self.FORMAT:
setattr(self, a.field_name, None)
if data:
self._from_data(data)
def _from_data(self, data):
self.signature = data[0:8]
for a in self.FORMAT:
setattr(self, a.field_name, struct.unpack(
a.format, data[a.start:a.start+a.len])[0])
if isinstance(data, str):
data = [ord(c) for c in data]
self.manufacturer_id = data[10] | data[11] << 8 | data[12] << 16
self.components = []
for i in range(8):
if data[20] & (1 << i):
self.components.append(i)
self.earliest_compatible_revision = \
VersionField(data[24:24 + VersionField.VERSION_FIELD_LEN])
self.firmware_revision = \
VersionField(data[26:26 + VersionField.VERSION_WITH_AUX_FIELD_LEN])
if self.oem_data_length:
self.oem_data = data[34:-1]
# XXX checksum check
self.checksum = data[34 + self.oem_data_length]
self.length = 34 + self.oem_data_length+1
def __str__(self):
str = []
str.append("HPM Upgrade Image header")
str.append(" Signature: %s" % self.signature)
str.append(" Format Version: %s" % self.format_version)
str.append(" Device ID: %s" % self.device_id)
str.append(" Manufacturer: %s" % self.manufacturer_id)
str.append(" Product ID: %s" % self.product_id)
str.append(" Time: %s" % self.time)
str.append(" Image Cap: 0x%02x" % self.capabilities)
str.append(" Components: %s" % self.components)
str.append(" Selftest Timeout: %s" % self.selftest_timeout)
str.append(" Rollback Timeout: %s" % self.rollback_timeout)
str.append(" Inacc. Timeout: %s" % self.inaccessibility_timeout)
str.append(" Earliest comp.: %s" % self.earliest_compatible_revision)
str.append(" firmware Revision:%s" % self.firmware_revision)
str.append(" OEM data len: %s" % self.oem_data_length)
return "\n".join(str)
class UpgradeActionRecord(object):
ACTIONS = (
"Backup",
"Prepare",
"Upload for Upgrade",
"Upload for Compare"
)
def __init__(self, data=None):
self.action_type = array('B', data)[0]
if data:
(self.action, self.components, self.checksum) \
= struct.unpack('BBB', data[0:3])
self.length = 3
@staticmethod
def create_from_data(data):
action_type = array('B', data)[0]
if action_type == ACTION_BACKUP_COMPONENT:
return UpgradeActionRecordBackup(data)
elif action_type == ACTION_PREPARE_COMPONENT:
return UpgradeActionRecordPrepare(data)
elif action_type == ACTION_UPLOAD_FOR_UPGRADE:
return UpgradeActionRecordUploadForUpgrade(data)
elif action_type == ACTION_UPLOAD_FOR_COMPARE:
return UpgradeActionRecordUploadForCompare(data)
else:
raise HpmError('unsupported ActionRecord')
def __str__(self):
str = []
str.append("Action Record Type: 0x%x (%s) " %
(self.action, self.ACTIONS[self.action]))
str.append(" Components: 0x%02x" % self.components)
return "\n".join(str)
class UpgradeActionRecordBackup(UpgradeActionRecord):
pass
class UpgradeActionRecordPrepare(UpgradeActionRecord):
pass
class UpgradeActionRecordUploadForUpgrade(UpgradeActionRecord):
def __init__(self, data=None):
UpgradeActionRecord.__init__(self, data)
if data:
self.firmware_version = \
VersionField(
data[3:3 + VersionField.VERSION_WITH_AUX_FIELD_LEN])
self.firmware_description_string \
= py3dec_unic_bytes_fix(data[9:30])
self.firmware_length = struct.unpack('<L', data[30:34])[0]
self.firmware_image_data = data[34:(34 + self.firmware_length)]
self.length += 31 + self.firmware_length
class UpgradeActionRecordUploadForCompare(UpgradeActionRecord):
pass
class ImageChecksumRecord(object):
def __init__(self, data=None):
if data:
self._from_data(data)
def _from_data(self, data):
self.data = data[0:16]
HPM_IMAGE_CHECKSUM_SIZE = 16
class UpgradeImage(object):
def __init__(self, filename=None):
self.actions = None
if filename:
self._from_file(filename)
def __str__(self):
str = []
return "\n".join(str)
def _check_md5_sum(self, filedata):
summer = hashlib.md5()
self.checksum_actual \
= summer.update(filedata[:-HPM_IMAGE_CHECKSUM_SIZE])
self.checksum_expected = filedata[-HPM_IMAGE_CHECKSUM_SIZE:]
def _from_file(self, 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)
################################
# get image checksum
self._check_md5_sum(file_data)
# XXX verify checksum
################################
# Upgrade Image Header
self.header = UpgradeImageHeaderRecord(file_data)
off = self.header.length
################################
# Upgrade Actions
self.actions = []
while (off + HPM_IMAGE_CHECKSUM_SIZE) < len(file_data):
action = UpgradeActionRecord.create_from_data(file_data[off:])
self.actions.append(action)
off += action.length
################################
# Image checksum
self.checksum = ImageChecksumRecord(file_data[off:file_size])
file.close()
Zerion Mini Shell 1.0