mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-09-01 19:02:31 +01:00
Initial commit of open source Workload Automation.
This commit is contained in:
16
wlauto/modules/__init__.py
Normal file
16
wlauto/modules/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2014-2015 ARM Limited
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
64
wlauto/modules/active_cooling.py
Normal file
64
wlauto/modules/active_cooling.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright 2014-2015 ARM Limited
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
|
||||
from wlauto import Module, Parameter
|
||||
from wlauto.utils.serial_port import open_serial_connection
|
||||
|
||||
|
||||
class MbedFanActiveCooling(Module):
|
||||
|
||||
name = 'mbed-fan'
|
||||
|
||||
capabilities = ['active_cooling']
|
||||
|
||||
parameters = [
|
||||
Parameter('port', default='/dev/ttyACM0',
|
||||
description="""The serial port for the active cooling solution (see above)."""),
|
||||
Parameter('buad', kind=int, default=115200,
|
||||
description="""Baud for the serial port (see above)."""),
|
||||
Parameter('fan_pin', kind=int, default=0,
|
||||
description="""Which controller pin on the mbed the fan for the active cooling solution is
|
||||
connected to (controller pin 0 is physical pin 22 on the mbed)."""),
|
||||
]
|
||||
|
||||
timeout = 30
|
||||
|
||||
def start_active_cooling(self):
|
||||
with open_serial_connection(timeout=self.timeout,
|
||||
port=self.port,
|
||||
baudrate=self.buad) as target:
|
||||
target.sendline('motor_{}_1'.format(self.fan_pin))
|
||||
|
||||
def stop_active_cooling(self):
|
||||
with open_serial_connection(timeout=self.timeout,
|
||||
port=self.port,
|
||||
baudrate=self.buad) as target:
|
||||
target.sendline('motor_{}_0'.format(self.fan_pin))
|
||||
|
||||
|
||||
class OdroidXU3ctiveCooling(Module):
|
||||
|
||||
name = 'odroidxu3-fan'
|
||||
|
||||
capabilities = ['active_cooling']
|
||||
|
||||
def start_active_cooling(self):
|
||||
self.owner.set_sysfile_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False)
|
||||
self.owner.set_sysfile_value('/sys/devices/odroid_fan.15/pwm_duty', 255, verify=False)
|
||||
|
||||
def stop_active_cooling(self):
|
||||
self.owner.set_sysfile_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False)
|
||||
self.owner.set_sysfile_value('/sys/devices/odroid_fan.15/pwm_duty', 1, verify=False)
|
253
wlauto/modules/flashing.py
Normal file
253
wlauto/modules/flashing.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# Copyright 2014-2015 ARM Limited
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
import os
|
||||
import time
|
||||
import tarfile
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from wlauto import Module, Parameter
|
||||
from wlauto.exceptions import ConfigError, DeviceError
|
||||
from wlauto.utils.android import fastboot_flash_partition, fastboot_command
|
||||
from wlauto.utils.serial_port import open_serial_connection
|
||||
from wlauto.utils.uefi import UefiMenu
|
||||
from wlauto.utils.types import boolean
|
||||
from wlauto.utils.misc import merge_dicts
|
||||
|
||||
|
||||
class Flasher(Module):
|
||||
"""
|
||||
Implements a mechanism for flashing a device. The images to be flashed can be
|
||||
specified either as a tarball "image bundle" (in which case instructions for
|
||||
flashing are provided as flasher-specific metadata also in the bundle), or as
|
||||
individual image files, in which case instructions for flashing as specified
|
||||
as part of flashing config.
|
||||
|
||||
.. note:: It is important that when resolving configuration, concrete flasher
|
||||
implementations prioritise settings specified in the config over those
|
||||
in the bundle (if they happen to clash).
|
||||
|
||||
"""
|
||||
|
||||
capabilities = ['flash']
|
||||
|
||||
def flash(self, image_bundle=None, images=None):
|
||||
"""
|
||||
Flashes the specified device using the specified config. As a post condition,
|
||||
the device must be ready to run workloads upon returning from this method (e.g.
|
||||
it must be fully-booted into the OS).
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FastbootFlasher(Flasher):
|
||||
|
||||
name = 'fastboot'
|
||||
description = """
|
||||
Enables automated flashing of images using the fastboot utility.
|
||||
To use this flasher, a set of image files to be flused are required.
|
||||
In addition a mapping between partitions and image file is required. There are two ways
|
||||
to specify those requirements:
|
||||
|
||||
- Image mapping: In this mode, a mapping between partitions and images is given in the agenda.
|
||||
- Image Bundle: In This mode a tarball is specified, which must contain all image files as well
|
||||
as well as a partition file, named ``partitions.txt`` which contains the mapping between
|
||||
partitions and images.
|
||||
|
||||
The format of ``partitions.txt`` defines one mapping per line as such: ::
|
||||
|
||||
kernel zImage-dtb
|
||||
ramdisk ramdisk_image
|
||||
|
||||
"""
|
||||
|
||||
delay = 0.5
|
||||
serial_timeout = 30
|
||||
partitions_file_name = 'partitions.txt'
|
||||
|
||||
def flash(self, image_bundle=None, images=None):
|
||||
self.prelude_done = False
|
||||
to_flash = {}
|
||||
if image_bundle: # pylint: disable=access-member-before-definition
|
||||
image_bundle = expand_path(image_bundle)
|
||||
to_flash = self._bundle_to_images(image_bundle)
|
||||
to_flash = merge_dicts(to_flash, images or {}, should_normalize=False)
|
||||
for partition, image_path in to_flash.iteritems():
|
||||
self.logger.debug('flashing {}'.format(partition))
|
||||
self._flash_image(self.owner, partition, expand_path(image_path))
|
||||
fastboot_command('reboot')
|
||||
|
||||
def _validate_image_bundle(self, image_bundle):
|
||||
if not tarfile.is_tarfile(image_bundle):
|
||||
raise ConfigError('File {} is not a tarfile'.format(image_bundle))
|
||||
with tarfile.open(image_bundle) as tar:
|
||||
files = [tf.name for tf in tar.getmembers()]
|
||||
if not any(pf in files for pf in (self.partitions_file_name, '{}/{}'.format(files[0], self.partitions_file_name))):
|
||||
ConfigError('Image bundle does not contain the required partition file (see documentation)')
|
||||
|
||||
def _bundle_to_images(self, image_bundle):
|
||||
"""
|
||||
Extracts the bundle to a temporary location and creates a mapping between the contents of the bundle
|
||||
and images to be flushed.
|
||||
"""
|
||||
self._validate_image_bundle(image_bundle)
|
||||
extract_dir = tempfile.mkdtemp()
|
||||
with tarfile.open(image_bundle) as tar:
|
||||
tar.extractall(path=extract_dir)
|
||||
files = [tf.name for tf in tar.getmembers()]
|
||||
if self.partitions_file_name not in files:
|
||||
extract_dir = os.path.join(extract_dir, files[0])
|
||||
partition_file = os.path.join(extract_dir, self.partitions_file_name)
|
||||
return get_mapping(extract_dir, partition_file)
|
||||
|
||||
def _flash_image(self, device, partition, image_path):
|
||||
if not self.prelude_done:
|
||||
self._fastboot_prelude(device)
|
||||
fastboot_flash_partition(partition, image_path)
|
||||
time.sleep(self.delay)
|
||||
|
||||
def _fastboot_prelude(self, device):
|
||||
with open_serial_connection(port=device.port,
|
||||
baudrate=device.baudrate,
|
||||
timeout=self.serial_timeout,
|
||||
init_dtr=0,
|
||||
get_conn=False) as target:
|
||||
device.reset()
|
||||
time.sleep(self.delay)
|
||||
target.sendline(' ')
|
||||
time.sleep(self.delay)
|
||||
target.sendline('fast')
|
||||
time.sleep(self.delay)
|
||||
self.prelude_done = True
|
||||
|
||||
|
||||
class VersatileExpressFlasher(Flasher):
|
||||
|
||||
name = 'vexpress'
|
||||
|
||||
parameters = [
|
||||
Parameter('image_name', default='Image',
|
||||
description='The name of the kernel image to boot.'),
|
||||
Parameter('image_args', default=None,
|
||||
description='Kernel arguments with which the image will be booted.'),
|
||||
Parameter('fdt_support', kind=boolean, default=True,
|
||||
description='Specifies whether the image has device tree support.'),
|
||||
Parameter('initrd', default=None,
|
||||
description='If the kernel image uses an INITRD, this can be used to specify it.'),
|
||||
Parameter('fdt_path', default=None,
|
||||
description='If specified, this will be set as the FDT path.'),
|
||||
]
|
||||
|
||||
def flash(self, image_bundle=None, images=None):
|
||||
device = self.owner
|
||||
if not hasattr(device, 'port') or not hasattr(device, 'microsd_mount_point'):
|
||||
msg = 'Device {} does not appear to support VExpress flashing.'
|
||||
raise ConfigError(msg.format(device.name))
|
||||
with open_serial_connection(port=device.port,
|
||||
baudrate=device.baudrate,
|
||||
timeout=device.timeout,
|
||||
init_dtr=0) as target:
|
||||
target.sendline('usb_on') # this will cause the MicroSD to be mounted on the host
|
||||
device.wait_for_microsd_mount_point(target)
|
||||
self.deploy_images(device, image_bundle, images)
|
||||
|
||||
self.logger.debug('Resetting the device.')
|
||||
device.hard_reset(target)
|
||||
|
||||
with open_serial_connection(port=device.port,
|
||||
baudrate=device.baudrate,
|
||||
timeout=device.timeout,
|
||||
init_dtr=0) as target:
|
||||
menu = UefiMenu(target)
|
||||
menu.open(timeout=120)
|
||||
if menu.has_option(device.uefi_entry):
|
||||
self.logger.debug('Deleting existing device entry.')
|
||||
menu.delete_entry(device.uefi_entry)
|
||||
self.create_uefi_enty(device, menu)
|
||||
menu.select(device.uefi_entry)
|
||||
target.expect(device.android_prompt, timeout=device.timeout)
|
||||
|
||||
def create_uefi_enty(self, device, menu):
|
||||
menu.create_entry(device.uefi_entry,
|
||||
self.image_name,
|
||||
self.image_args,
|
||||
self.fdt_support,
|
||||
self.initrd,
|
||||
self.fdt_path)
|
||||
|
||||
def deploy_images(self, device, image_bundle=None, images=None):
|
||||
try:
|
||||
if image_bundle:
|
||||
self.deploy_image_bundle(device, image_bundle)
|
||||
if images:
|
||||
self.overlay_images(device, images)
|
||||
os.system('sync')
|
||||
except (IOError, OSError), e:
|
||||
msg = 'Could not deploy images to {}; got: {}'
|
||||
raise DeviceError(msg.format(device.microsd_mount_point, e))
|
||||
|
||||
def deploy_image_bundle(self, device, bundle):
|
||||
self.logger.debug('Validating {}'.format(bundle))
|
||||
validate_image_bundle(bundle)
|
||||
self.logger.debug('Extracting {} into {}...'.format(bundle, device.microsd_mount_point))
|
||||
with tarfile.open(bundle) as tar:
|
||||
tar.extractall(device.microsd_mount_point)
|
||||
|
||||
def overlay_images(self, device, images):
|
||||
for dest, src in images.iteritems():
|
||||
dest = os.path.join(device.microsd_mount_point, dest)
|
||||
self.logger.debug('Copying {} to {}'.format(src, dest))
|
||||
shutil.copy(src, dest)
|
||||
|
||||
|
||||
# utility functions
|
||||
|
||||
def get_mapping(base_dir, partition_file):
|
||||
mapping = {}
|
||||
with open(partition_file) as pf:
|
||||
for line in pf:
|
||||
pair = line.split()
|
||||
if len(pair) != 2:
|
||||
ConfigError('partitions.txt is not properly formated')
|
||||
image_path = os.path.join(base_dir, pair[1])
|
||||
if not os.path.isfile(expand_path(image_path)):
|
||||
ConfigError('file {} was not found in the bundle or was misplaced'.format(pair[1]))
|
||||
mapping[pair[0]] = image_path
|
||||
return mapping
|
||||
|
||||
|
||||
def expand_path(original_path):
|
||||
path = os.path.abspath(os.path.expanduser(original_path))
|
||||
if not os.path.exists(path):
|
||||
raise ConfigError('{} does not exist.'.format(path))
|
||||
return path
|
||||
|
||||
|
||||
def validate_image_bundle(bundle):
|
||||
if not tarfile.is_tarfile(bundle):
|
||||
raise ConfigError('Image bundle {} does not appear to be a valid TAR file.'.format(bundle))
|
||||
with tarfile.open(bundle) as tar:
|
||||
try:
|
||||
tar.getmember('config.txt')
|
||||
except KeyError:
|
||||
try:
|
||||
tar.getmember('./config.txt')
|
||||
except KeyError:
|
||||
msg = 'Tarball {} does not appear to be a valid image bundle (did not see config.txt).'
|
||||
raise ConfigError(msg.format(bundle))
|
||||
|
52
wlauto/modules/reset.py
Normal file
52
wlauto/modules/reset.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright 2014-2015 ARM Limited
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from wlauto import Module, Parameter
|
||||
from wlauto.exceptions import DeviceError
|
||||
from wlauto.utils.netio import KshellConnection
|
||||
|
||||
|
||||
class NetioSwitchReset(Module):
|
||||
|
||||
#pylint: disable=E1101
|
||||
name = 'netio_switch'
|
||||
capabilities = ['reset_power']
|
||||
|
||||
parameters = [
|
||||
Parameter('host', default='ippowerbar',
|
||||
description='IP address or DNS name of the Netio power switch.'),
|
||||
Parameter('port', kind=int, default=1234,
|
||||
description='Port on which KSHELL is listening.'),
|
||||
Parameter('username', default='admin',
|
||||
description='User name for the administrator on the Netio.'),
|
||||
Parameter('password', default='admin',
|
||||
description='User name for the administrator on the Netio.'),
|
||||
Parameter('psu', kind=int, default=1,
|
||||
description='The device port number on the Netio, i.e. which '
|
||||
'PSU port the device is connected to.'),
|
||||
]
|
||||
|
||||
def hard_reset(self):
|
||||
try:
|
||||
conn = KshellConnection(host=self.host, port=self.port)
|
||||
conn.login(self.username, self.password)
|
||||
conn.disable_port(self.psu)
|
||||
time.sleep(2)
|
||||
conn.enable_port(self.psu)
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
raise DeviceError('Could not reset power: {}'.format(e))
|
Reference in New Issue
Block a user