1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-11-18 06:35:17 +00:00

devlib initial commit.

This commit is contained in:
Sergei Trofimov
2015-10-09 09:30:04 +01:00
commit 4e6afe960b
64 changed files with 8938 additions and 0 deletions

122
devlib/module/__init__.py Normal file
View File

@@ -0,0 +1,122 @@
# 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 logging
from inspect import isclass
from devlib.utils.misc import walk_modules
from devlib.utils.types import identifier
__module_cache = {}
class Module(object):
name = None
kind = None
# This is the stage at which the module will be installed. Current valid
# stages are:
# 'early' -- installed when the Target is first created. This should be
# used for modules that do not rely on the main connection
# being established (usually because the commumnitcate with the
# target through some sorto of secondary connection, e.g. via
# serial).
# 'connected' -- installed when a connection to to the target has been
# established. This is the default.
stage = 'connected'
@staticmethod
def probe(target):
raise NotImplementedError()
@classmethod
def install(cls, target, **params):
if cls.kind is not None:
attr_name = identifier(cls.kind)
else:
attr_name = identifier(cls.name)
if hasattr(target, attr_name):
existing_module = getattr(target, attr_name)
existing_name = getattr(existing_module, 'name', str(existing_module))
message = 'Attempting to install module "{}" which already exists (new: {}, existing: {})'
raise ValueError(message.format(attr_name, cls.name, existing_name))
setattr(target, attr_name, cls(target, **params))
def __init__(self, target):
self.target = target
self.logger = logging.getLogger(self.__class__.__name__)
class HardRestModule(Module): # pylint: disable=R0921
kind = 'hard_reset'
def __call__(self):
raise NotImplementedError()
class BootModule(Module): # pylint: disable=R0921
kind = 'boot'
def __call__(self):
raise NotImplementedError()
def update(self, **kwargs):
for name, value in kwargs.iteritems():
if not hasattr(self, name):
raise ValueError('Unknown parameter "{}" for {}'.format(name, self.name))
self.logger.debug('Updating "{}" to "{}"'.format(name, value))
setattr(self, name, value)
class FlashModule(Module):
kind = 'flash'
def __call__(self, image_bundle=None, images=None, boot_config=None):
raise NotImplementedError()
def get_module(mod):
if not __module_cache:
__load_cache()
if isinstance(mod, basestring):
try:
return __module_cache[mod]
except KeyError:
raise ValueError('Module "{}" does not exist'.format(mod))
elif issubclass(mod, Module):
return mod
else:
raise ValueError('Not a valid module: {}'.format(mod))
def register_module(mod):
if not issubclass(mod, Module):
raise ValueError('A module must subclass devlib.Module')
if mod.name is None:
raise ValueError('A module must define a name')
if mod.name in __module_cache:
raise ValueError('Module {} already exists'.format(mod.name))
__module_cache[mod.name] = mod
def __load_cache():
for module in walk_modules('devlib.module'):
for obj in vars(module).itervalues():
if isclass(obj) and issubclass(obj, Module) and obj.name:
register_module(obj)

128
devlib/module/android.py Normal file
View File

@@ -0,0 +1,128 @@
# 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
from devlib.module import FlashModule
from devlib.exception import HostError
from devlib.utils.android import fastboot_flash_partition, fastboot_command
from devlib.utils.misc import merge_dicts
class FastbootFlashModule(FlashModule):
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
partitions_file_name = 'partitions.txt'
@staticmethod
def probe(target):
return target.os == 'android'
def __call__(self, image_bundle=None, images=None, bootargs=None):
if bootargs:
raise ValueError('{} does not support boot configuration'.format(self.name))
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.target, partition, expand_path(image_path))
fastboot_command('reboot')
self.target.connect(timeout=180)
def _validate_image_bundle(self, image_bundle):
if not tarfile.is_tarfile(image_bundle):
raise HostError('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))):
HostError('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, target, partition, image_path):
if not self.prelude_done:
self._fastboot_prelude(target)
fastboot_flash_partition(partition, image_path)
time.sleep(self.delay)
def _fastboot_prelude(self, target):
target.reset(fastboot=True)
time.sleep(self.delay)
self.prelude_done = True
# utility functions
def expand_path(original_path):
path = os.path.abspath(os.path.expanduser(original_path))
if not os.path.exists(path):
raise HostError('{} does not exist.'.format(path))
return path
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:
HostError('partitions.txt is not properly formated')
image_path = os.path.join(base_dir, pair[1])
if not os.path.isfile(expand_path(image_path)):
HostError('file {} was not found in the bundle or was misplaced'.format(pair[1]))
mapping[pair[0]] = image_path
return mapping

122
devlib/module/biglittle.py Normal file
View File

@@ -0,0 +1,122 @@
from devlib.module import Module
class BigLittleModule(Module):
name = 'bl'
@staticmethod
def probe(target):
return target.big_core is not None
@property
def bigs(self):
return [i for i, c in enumerate(self.target.platform.core_names)
if c == self.target.platform.big_core]
@property
def littles(self):
return [i for i, c in enumerate(self.target.platform.core_names)
if c == self.target.platform.little_core]
@property
def bigs_online(self):
return list(sorted(set(self.bigs).intersection(self.target.list_online_cpus())))
@property
def littles_online(self):
return list(sorted(set(self.littles).intersection(self.target.list_online_cpus())))
# hotplug
def online_all_bigs(self):
self.target.hotplug.online(*self.bigs)
def offline_all_bigs(self):
self.target.hotplug.offline(*self.bigs)
def online_all_littles(self):
self.target.hotplug.online(*self.littles)
def offline_all_littles(self):
self.target.hotplug.offline(*self.littles)
# cpufreq
def list_bigs_frequencies(self):
return self.target.cpufreq.list_frequencies(self.bigs_online[0])
def list_bigs_governors(self):
return self.target.cpufreq.list_governors(self.bigs_online[0])
def list_bigs_governor_tunables(self):
return self.target.cpufreq.list_governor_tunables(self.bigs_online[0])
def list_littles_frequencies(self):
return self.target.cpufreq.list_frequencies(self.littles_online[0])
def list_littles_governors(self):
return self.target.cpufreq.list_governors(self.littles_online[0])
def list_littles_governor_tunables(self):
return self.target.cpufreq.list_governor_tunables(self.littles_online[0])
def get_bigs_governor(self):
return self.target.cpufreq.get_governor(self.bigs_online[0])
def get_bigs_governor_tunables(self):
return self.target.cpufreq.get_governor_tunables(self.bigs_online[0])
def get_bigs_frequency(self):
return self.target.cpufreq.get_frequency(self.bigs_online[0])
def get_bigs_min_frequency(self):
return self.target.cpufreq.get_min_frequency(self.bigs_online[0])
def get_bigs_max_frequency(self):
return self.target.cpufreq.get_max_frequency(self.bigs_online[0])
def get_littles_governor(self):
return self.target.cpufreq.get_governor(self.littles_online[0])
def get_littles_governor_tunables(self):
return self.target.cpufreq.get_governor_tunables(self.littles_online[0])
def get_littles_frequency(self):
return self.target.cpufreq.get_frequency(self.littles_online[0])
def get_littles_min_frequency(self):
return self.target.cpufreq.get_min_frequency(self.littles_online[0])
def get_littles_max_frequency(self):
return self.target.cpufreq.get_max_frequency(self.littles_online[0])
def set_bigs_governor(self, governor, **kwargs):
self.target.cpufreq.set_governor(self.bigs_online[0], governor, **kwargs)
def set_bigs_governor_tunables(self, governor, **kwargs):
self.target.cpufreq.set_governor_tunables(self.bigs_online[0], governor, **kwargs)
def set_bigs_frequency(self, frequency, exact=True):
self.target.cpufreq.set_frequency(self.bigs_online[0], frequency, exact)
def set_bigs_min_frequency(self, frequency, exact=True):
self.target.cpufreq.set_min_frequency(self.bigs_online[0], frequency, exact)
def set_bigs_max_frequency(self, frequency, exact=True):
self.target.cpufreq.set_max_frequency(self.bigs_online[0], frequency, exact)
def set_littles_governor(self, governor, **kwargs):
self.target.cpufreq.set_governor(self.littles_online[0], governor, **kwargs)
def set_littles_governor_tunables(self, governor, **kwargs):
self.target.cpufreq.set_governor_tunables(self.littles_online[0], governor, **kwargs)
def set_littles_frequency(self, frequency, exact=True):
self.target.cpufreq.set_frequency(self.littles_online[0], frequency, exact)
def set_littles_min_frequency(self, frequency, exact=True):
self.target.cpufreq.set_min_frequency(self.littles_online[0], frequency, exact)
def set_littles_max_frequency(self, frequency, exact=True):
self.target.cpufreq.set_max_frequency(self.littles_online[0], frequency, exact)

206
devlib/module/cgroups.py Normal file
View File

@@ -0,0 +1,206 @@
# 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 logging
from collections import namedtuple
from devlib.module import Module
from devlib.exception import TargetError
from devlib.utils.misc import list_to_ranges, isiterable
from devlib.utils.types import boolean
class CgroupController(object):
kind = 'cpuset'
def __new__(cls, arg):
if isinstance(arg, cls):
return arg
else:
return object.__new__(cls, arg)
def __init__(self, mount_name):
self.mount_point = None
self.mount_name = mount_name
self.logger = logging.getLogger(self.kind)
def probe(self, target):
raise NotImplementedError()
def mount(self, device, mount_root):
self.target = device
self.mount_point = device.path.join(mount_root, self.mount_name)
mounted = self.target.list_file_systems()
if self.mount_point in [e.mount_point for e in mounted]:
self.logger.debug('controller is already mounted.')
else:
self.target.execute('mkdir -p {} 2>/dev/null'.format(self.mount_point),
as_root=True)
self.target.execute('mount -t cgroup -o {} {} {}'.format(self.kind,
self.mount_name,
self.mount_point),
as_root=True)
class CpusetGroup(object):
def __init__(self, controller, name, cpus, mems):
self.controller = controller
self.target = controller.target
self.name = name
if name == 'root':
self.directory = controller.mount_point
else:
self.directory = self.target.path.join(controller.mount_point, name)
self.target.execute('mkdir -p {}'.format(self.directory), as_root=True)
self.cpus_file = self.target.path.join(self.directory, 'cpuset.cpus')
self.mems_file = self.target.path.join(self.directory, 'cpuset.mems')
self.tasks_file = self.target.path.join(self.directory, 'tasks')
self.set(cpus, mems)
def set(self, cpus, mems):
if isiterable(cpus):
cpus = list_to_ranges(cpus)
if isiterable(mems):
mems = list_to_ranges(mems)
self.target.write_value(self.cpus_file, cpus)
self.target.write_value(self.mems_file, mems)
def get(self):
cpus = self.target.read_value(self.cpus_file)
mems = self.target.read_value(self.mems_file)
return (cpus, mems)
def get_tasks(self):
task_ids = self.target.read_value(self.tasks_file).split()
return map(int, task_ids)
def add_tasks(self, tasks):
for tid in tasks:
self.add_task(tid)
def add_task(self, tid):
self.target.write_value(self.tasks_file, tid, verify=False)
class CpusetController(CgroupController):
name = 'cpuset'
def __init__(self, *args, **kwargs):
super(CpusetController, self).__init__(*args, **kwargs)
self.groups = {}
def probe(self, target):
return target.config.is_enabled('cpusets')
def mount(self, device, mount_root):
super(CpusetController, self).mount(device, mount_root)
self.create_group('root', self.target.list_online_cpus(), 0)
def create_group(self, name, cpus, mems):
if not hasattr(self, 'target'):
raise RuntimeError('Attempting to create group for unmounted controller {}'.format(self.kind))
if name in self.groups:
raise ValueError('Group {} already exists'.format(name))
self.groups[name] = CpusetGroup(self, name, cpus, mems)
def move_tasks(self, source, dest):
try:
source_group = self.groups[source]
dest_group = self.groups[dest]
command = 'for task in $(cat {}); do echo $task>{}; done'
self.target.execute(command.format(source_group.tasks_file, dest_group.tasks_file),
# this will always fail as some of the tasks
# are kthreads that cannot be migrated, but we
# don't care about those, so don't check exit
# code.
check_exit_code=False, as_root=True)
except KeyError as e:
raise ValueError('Unkown group: {}'.format(e))
def move_all_tasks_to(self, target_group):
for group in self.groups:
if group != target_group:
self.move_tasks(group, target_group)
def __getattr__(self, name):
try:
return self.groups[name]
except KeyError:
raise AttributeError(name)
CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled')
class CgroupsModule(Module):
name = 'cgroups'
controller_cls = [
CpusetController,
]
cgroup_root = '/sys/fs/cgroup'
@staticmethod
def probe(target):
return target.config.has('cgroups') and target.is_rooted
def __init__(self, target):
super(CgroupsModule, self).__init__(target)
mounted = self.target.list_file_systems()
if self.cgroup_root not in [e.mount_point for e in mounted]:
self.target.execute('mount -t tmpfs {} {}'.format('cgroup_root', self.cgroup_root),
as_root=True)
else:
self.logger.debug('cgroup_root already mounted at {}'.format(self.cgroup_root))
self.controllers = []
for cls in self.controller_cls:
controller = cls('devlib_{}'.format(cls.name))
if controller.probe(self.target):
if controller.mount_name in [e.device for e in mounted]:
self.logger.debug('controller {} is already mounted.'.format(controller.kind))
else:
try:
controller.mount(self.target, self.cgroup_root)
except TargetError:
message = 'cgroups {} controller is not supported by the target'
raise TargetError(message.format(controller.kind))
def list_subsystems(self):
subsystems = []
for line in self.target.execute('cat /proc/cgroups').split('\n')[1:]:
line = line.strip()
if not line or line.startswith('#'):
continue
name, hierarchy, num_cgroups, enabled = line.split()
subsystems.append(CgroupSubsystemEntry(name,
int(hierarchy),
int(num_cgroups),
boolean(enabled)))
return subsystems
def get_cgroup_controller(self, kind):
for controller in self.controllers:
if controller.kind == kind:
return controller
raise ValueError(kind)

63
devlib/module/cooling.py Normal file
View File

@@ -0,0 +1,63 @@
# 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 devlib.module import Module
from devlib.utils.serial_port import open_serial_connection
class MbedFanActiveCoolingModule(Module):
name = 'mbed-fan'
timeout = 30
@staticmethod
def probe(target):
return True
def __init__(self, target, port='/dev/ttyACM0', baud=115200, fan_pin=0):
super(MbedFanActiveCoolingModule, self).__init__(target)
self.port = port
self.baud = baud
self.fan_pin = fan_pin
def start(self):
with open_serial_connection(timeout=self.timeout,
port=self.port,
baudrate=self.baud) as target:
target.sendline('motor_{}_1'.format(self.fan_pin))
def stop(self):
with open_serial_connection(timeout=self.timeout,
port=self.port,
baudrate=self.baud) as target:
target.sendline('motor_{}_0'.format(self.fan_pin))
class OdroidXU3ctiveCoolingModule(Module):
name = 'odroidxu3-fan'
@staticmethod
def probe(target):
return target.file_exists('/sys/devices/odroid_fan.15/fan_mode')
def start(self):
self.target.write_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False)
self.target.write_value('/sys/devices/odroid_fan.15/pwm_duty', 255, verify=False)
def stop(self):
self.target.write_value('/sys/devices/odroid_fan.15/fan_mode', 0, verify=False)
self.target.write_value('/sys/devices/odroid_fan.15/pwm_duty', 1, verify=False)

315
devlib/module/cpufreq.py Normal file
View File

@@ -0,0 +1,315 @@
# 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 devlib.module import Module
from devlib.exception import TargetError
from devlib.utils.misc import memoized
# a dict of governor name and a list of it tunables that can't be read
WRITE_ONLY_TUNABLES = {
'interactive': ['boostpulse']
}
class CpufreqModule(Module):
name = 'cpufreq'
@staticmethod
def probe(target):
path = '/sys/devices/system/cpu/cpufreq'
return target.file_exists(path)
def __init__(self, target):
super(CpufreqModule, self).__init__(target)
self._governor_tunables = {}
@memoized
def list_governors(self, cpu):
"""Returns a list of governors supported by the cpu."""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu)
output = self.target.read_value(sysfile)
return output.strip().split()
def get_governor(self, cpu):
"""Returns the governor currently set for the specified CPU."""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
return self.target.read_value(sysfile)
def set_governor(self, cpu, governor, **kwargs):
"""
Set the governor for the specified CPU.
See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
:param cpu: The CPU for which the governor is to be set. This must be
the full name as it appears in sysfs, e.g. "cpu0".
:param governor: The name of the governor to be used. This must be
supported by the specific device.
Additional keyword arguments can be used to specify governor tunables for
governors that support them.
:note: On big.LITTLE all cores in a cluster must be using the same governor.
Setting the governor on any core in a cluster will also set it on all
other cores in that cluster.
:raises: TargetError if governor is not supported by the CPU, or if,
for some reason, the governor could not be set.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
supported = self.list_governors(cpu)
if governor not in supported:
raise TargetError('Governor {} not supported for cpu {}'.format(governor, cpu))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
self.target.write_value(sysfile, governor)
self.set_governor_tunables(cpu, governor, **kwargs)
def list_governor_tunables(self, cpu):
"""Returns a list of tunables available for the governor on the specified CPU."""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
governor = self.get_governor(cpu)
if governor not in self._governor_tunables:
try:
tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor)
self._governor_tunables[governor] = self.target.list_directory(tunables_path)
except TargetError: # probably an older kernel
try:
tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor)
self._governor_tunables[governor] = self.target.list_directory(tunables_path)
except TargetError: # governor does not support tunables
self._governor_tunables[governor] = []
return self._governor_tunables[governor]
def get_governor_tunables(self, cpu):
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
governor = self.get_governor(cpu)
tunables = {}
for tunable in self.list_governor_tunables(cpu):
if tunable not in WRITE_ONLY_TUNABLES.get(governor, []):
try:
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
tunables[tunable] = self.target.read_value(path)
except TargetError: # May be an older kernel
path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
tunables[tunable] = self.target.read_value(path)
return tunables
def set_governor_tunables(self, cpu, governor=None, **kwargs):
"""
Set tunables for the specified governor. Tunables should be specified as
keyword arguments. Which tunables and values are valid depends on the
governor.
:param cpu: The cpu for which the governor will be set. This must be the
full cpu name as it appears in sysfs, e.g. ``cpu0``.
:param governor: The name of the governor. Must be all lower case.
The rest should be keyword parameters mapping tunable name onto the value to
be set for it.
:raises: TargetError if governor specified is not a valid governor name, or if
a tunable specified is not valid for the governor, or if could not set
tunable.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
if governor is None:
governor = self.get_governor(cpu)
valid_tunables = self.list_governor_tunables(cpu)
for tunable, value in kwargs.iteritems():
if tunable in valid_tunables:
try:
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
self.target.write_value(path, value)
except TargetError: # May be an older kernel
path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
self.target.write_value(path, value)
else:
message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu)
message += 'Available tunables are: {}'.format(valid_tunables)
raise TargetError(message)
@memoized
def list_frequencies(self, cpu):
"""Returns a list of frequencies supported by the cpu or an empty list
if not could be found."""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
try:
cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu)
output = self.target.execute(cmd)
available_frequencies = map(int, output.strip().split()) # pylint: disable=E1103
except TargetError:
# On some devices scaling_frequencies is not generated.
# http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html
# Fall back to parsing stats/time_in_state
cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu)
out_iter = iter(self.target.execute(cmd).strip().split())
available_frequencies = map(int, reversed([f for f, _ in zip(out_iter, out_iter)]))
return available_frequencies
def get_min_frequency(self, cpu):
"""
Returns the min frequency currently set for the specified CPU.
Warning, this method does not check if the cpu is online or not. It will
try to read the minimum frequency and the following exception will be
raised ::
:raises: TargetError if for some reason the frequency could not be read.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu)
return self.target.read_int(sysfile)
def set_min_frequency(self, cpu, frequency, exact=True):
"""
Set's the minimum value for CPU frequency. Actual frequency will
depend on the Governor used and may vary during execution. The value should be
either an int or a string representing an integer. The Value must also be
supported by the device. The available frequencies can be obtained by calling
get_frequencies() or examining
/sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
on the device.
:raises: TargetError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
available_frequencies = self.list_frequencies(cpu)
try:
value = int(frequency)
if exact and available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value,
available_frequencies))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu)
self.target.write_value(sysfile, value)
except ValueError:
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
def get_frequency(self, cpu):
"""
Returns the current frequency currently set for the specified CPU.
Warning, this method does not check if the cpu is online or not. It will
try to read the current frequency and the following exception will be
raised ::
:raises: TargetError if for some reason the frequency could not be read.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq'.format(cpu)
return self.target.read_int(sysfile)
def set_frequency(self, cpu, frequency, exact=True):
"""
Set's the minimum value for CPU frequency. Actual frequency will
depend on the Governor used and may vary during execution. The value should be
either an int or a string representing an integer.
If ``exact`` flag is set (the default), the Value must also be supported by
the device. The available frequencies can be obtained by calling
get_frequencies() or examining
/sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
on the device (if it exists).
:raises: TargetError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
try:
value = int(frequency)
if exact:
available_frequencies = self.list_frequencies(cpu)
if available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value,
available_frequencies))
if self.get_governor(cpu) != 'userspace':
raise TargetError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu)
self.target.write_value(sysfile, value, verify=False)
except ValueError:
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
def get_max_frequency(self, cpu):
"""
Returns the max frequency currently set for the specified CPU.
Warning, this method does not check if the cpu is online or not. It will
try to read the maximum frequency and the following exception will be
raised ::
:raises: TargetError if for some reason the frequency could not be read.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu)
return self.target.read_int(sysfile)
def set_max_frequency(self, cpu, frequency, exact=True):
"""
Set's the minimum value for CPU frequency. Actual frequency will
depend on the Governor used and may vary during execution. The value should be
either an int or a string representing an integer. The Value must also be
supported by the device. The available frequencies can be obtained by calling
get_frequencies() or examining
/sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
on the device.
:raises: TargetError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer.
"""
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
available_frequencies = self.list_frequencies(cpu)
try:
value = int(frequency)
if exact and available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value,
available_frequencies))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu)
self.target.write_value(sysfile, value)
except ValueError:
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))

138
devlib/module/cpuidle.py Normal file
View File

@@ -0,0 +1,138 @@
# 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
from devlib.module import Module
from devlib.utils.misc import memoized
from devlib.utils.types import integer, boolean
class CpuidleState(object):
@property
def usage(self):
return integer(self.get('usage'))
@property
def time(self):
return integer(self.get('time'))
@property
def is_enabled(self):
return not boolean(self.get('disable'))
@property
def ordinal(self):
i = len(self.id)
while self.id[i - 1].isdigit():
i -= 1
if not i:
raise ValueError('invalid idle state name: "{}"'.format(self.id))
return int(self.id[i:])
def __init__(self, target, index, path):
self.target = target
self.index = index
self.path = path
self.id = self.target.path.basename(self.path)
self.cpu = self.target.path.basename(self.target.path.dirname(path))
self.desc = self.get('desc')
self.name = self.get('name')
self.latency = self.get('latency')
self.power = self.get('power')
def enable(self):
self.set('disable', 0)
def disable(self):
self.set('disable', 1)
def get(self, prop):
property_path = self.target.path.join(self.path, prop)
return self.target.read_value(property_path)
def set(self, prop, value):
property_path = self.target.path.join(self.path, prop)
self.target.write_value(property_path, value)
def __eq__(self, other):
if isinstance(other, CpuidleState):
return (self.name == other.name) and (self.desc == other.desc)
elif isinstance(other, basestring):
return (self.name == other) or (self.desc == other)
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return 'CpuidleState({}, {})'.format(self.name, self.desc)
__repr__ = __str__
class Cpuidle(Module):
name = 'cpuidle'
root_path = '/sys/devices/system/cpu/cpuidle'
@staticmethod
def probe(target):
return target.file_exists(Cpuidle.root_path)
def get_driver(self):
return self.target.read_value(self.target.path.join(self.root_path, 'current_driver'))
def get_governor(self):
return self.target.read_value(self.target.path.join(self.root_path, 'current_governor_ro'))
@memoized
def get_states(self, cpu=0):
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
states_dir = self.target.path.join(self.target.path.dirname(self.root_path), cpu, 'cpuidle')
idle_states = []
for state in self.target.list_directory(states_dir):
if state.startswith('state'):
index = int(state[5:])
idle_states.append(CpuidleState(self.target, index, self.target.path.join(states_dir, state)))
return idle_states
def get_state(self, state, cpu=0):
if isinstance(state, int):
try:
self.get_states(cpu)[state].enable()
except IndexError:
raise ValueError('Cpuidle state {} does not exist'.format(state))
else: # assume string-like
for s in self.get_states(cpu):
if state in [s.id, s.name, s.desc]:
return s
raise ValueError('Cpuidle state {} does not exist'.format(state))
def enable(self, state, cpu=0):
self.get_state(state, cpu).enable()
def disable(self, state, cpu=0):
self.get_state(state, cpu).disable()
def enable_all(self, cpu=0):
for state in self.get_states(cpu):
state.enable()
def disable_all(self, cpu=0):
for state in self.get_states(cpu):
state.disable()

35
devlib/module/hotplug.py Normal file
View File

@@ -0,0 +1,35 @@
from devlib.module import Module
class HotplugModule(Module):
name = 'hotplug'
base_path = '/sys/devices/system/cpu'
@classmethod
def probe(cls, target): # pylint: disable=arguments-differ
path = cls._cpu_path(target, 0)
return target.file_exists(path) and target.is_rooted
@classmethod
def _cpu_path(cls, target, cpu):
if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu)
return target.path.join(cls.base_path, cpu, 'online')
def online_all(self):
self.online(*range(self.target.number_of_cpus))
def online(self, *args):
for cpu in args:
self.hotplug(cpu, online=True)
def offline(self, *args):
for cpu in args:
self.hotplug(cpu, online=False)
def hotplug(self, cpu, online):
path = self._cpu_path(self.target, cpu)
value = 1 if online else 0
self.target.write_value(path, value)

140
devlib/module/hwmon.py Normal file
View File

@@ -0,0 +1,140 @@
# Copyright 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 re
from collections import defaultdict
from devlib.module import Module
from devlib.utils.types import integer
HWMON_ROOT = '/sys/class/hwmon'
HWMON_FILE_REGEX = re.compile(r'(?P<kind>\w+?)(?P<number>\d+)_(?P<item>\w+)')
class HwmonSensor(object):
def __init__(self, device, path, kind, number):
self.device = device
self.path = path
self.kind = kind
self.number = number
self.target = self.device.target
self.name = '{}/{}{}'.format(self.device.name, self.kind, self.number)
self.label = self.name
self.items = set()
def add(self, item):
self.items.add(item)
if item == 'label':
self.label = self.get('label')
def get(self, item):
path = self.get_file(item)
value = self.target.read_value(path)
try:
return integer(value)
except (TypeError, ValueError):
return value
def set(self, item, value):
path = self.get_file(item)
self.target.write_value(path, value)
def get_file(self, item):
if item not in self.items:
raise ValueError('item "{}" does not exist for {}'.format(item, self.name))
filename = '{}{}_{}'.format(self.kind, self.number, item)
return self.target.path.join(self.path, filename)
def __str__(self):
if self.name != self.label:
text = 'HS({}, {})'.format(self.name, self.label)
else:
text = 'HS({})'.format(self.name)
return text
__repr__ = __str__
class HwmonDevice(object):
@property
def sensors(self):
all_sensors = []
for sensors_of_kind in self._sensors.itervalues():
all_sensors.extend(sensors_of_kind.values())
return all_sensors
def __init__(self, target, path):
self.target = target
self.path = path
self.name = self.target.read_value(self.target.path.join(self.path, 'name'))
self._sensors = defaultdict(dict)
path = self.path
if not path.endswith(self.target.path.sep):
path += self.target.path.sep
for entry in self.target.list_directory(path):
match = HWMON_FILE_REGEX.search(entry)
if match:
kind = match.group('kind')
number = int(match.group('number'))
item = match.group('item')
if number not in self._sensors[kind]:
sensor = HwmonSensor(self, self.path, kind, number)
self._sensors[kind][number] = sensor
self._sensors[kind][number].add(item)
def get(self, kind, number=None):
if number is None:
return [s for _, s in sorted(self._sensors[kind].iteritems(),
key=lambda x: x[0])]
else:
return self._sensors[kind].get(number)
def __str__(self):
return 'HD({})'.format(self.name)
__repr__ = __str__
class HwmonModule(Module):
name = 'hwmon'
@staticmethod
def probe(target):
return target.file_exists(HWMON_ROOT)
@property
def sensors(self):
all_sensors = []
for device in self.devices:
all_sensors.extend(device.sensors)
return all_sensors
def __init__(self, target):
super(HwmonModule, self).__init__(target)
self.root = HWMON_ROOT
self.devices = []
self.scan()
def scan(self):
for entry in self.target.list_directory(self.root):
if entry.startswith('hwmon'):
entry_path = self.target.path.join(self.root, entry)
if self.target.file_exists(self.target.path.join(entry_path, 'name')):
device = HwmonDevice(self.target, entry_path)
self.devices.append(device)

386
devlib/module/vexpress.py Normal file
View File

@@ -0,0 +1,386 @@
#
# Copyright 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 os
import time
import tarfile
import shutil
from devlib.module import HardRestModule, BootModule, FlashModule
from devlib.exception import TargetError, HostError
from devlib.utils.serial_port import open_serial_connection, pulse_dtr, write_characters
from devlib.utils.uefi import UefiMenu, UefiConfig
from devlib.utils.uboot import UbootMenu
AUTOSTART_MESSAGE = 'Press Enter to stop auto boot...'
POWERUP_MESSAGE = 'Powering up system...'
DEFAULT_MCC_PROMPT = 'Cmd>'
class VexpressDtrHardReset(HardRestModule):
name = 'vexpress-dtr'
stage = 'early'
@staticmethod
def probe(target):
return True
def __init__(self, target, port='/dev/ttyS0', baudrate=115200,
mcc_prompt=DEFAULT_MCC_PROMPT, timeout=300):
super(VexpressDtrHardReset, self).__init__(target)
self.port = port
self.baudrate = baudrate
self.mcc_prompt = mcc_prompt
self.timeout = timeout
def __call__(self):
try:
if self.target.is_connected:
self.target.execute('sync')
except TargetError:
pass
with open_serial_connection(port=self.port,
baudrate=self.baudrate,
timeout=self.timeout,
init_dtr=0,
get_conn=True) as (_, conn):
pulse_dtr(conn, state=True, duration=0.1) # TRM specifies a pulse of >=100ms
class VexpressReboottxtHardReset(HardRestModule):
name = 'vexpress-reboottxt'
stage = 'early'
@staticmethod
def probe(target):
return True
def __init__(self, target,
port='/dev/ttyS0', baudrate=115200,
path='/media/VEMSD',
mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1):
super(VexpressReboottxtHardReset, self).__init__(target)
self.port = port
self.baudrate = baudrate
self.path = path
self.mcc_prompt = mcc_prompt
self.timeout = timeout
self.short_delay = short_delay
self.filepath = os.path.join(path, 'reboot.txt')
def __call__(self):
try:
if self.target.is_connected:
self.target.execute('sync')
except TargetError:
pass
if not os.path.exists(self.path):
self.logger.debug('{} does not exisit; attempting to mount...'.format(self.path))
with open_serial_connection(port=self.port,
baudrate=self.baudrate,
timeout=self.timeout,
init_dtr=0) as tty:
wait_for_vemsd(self.path, tty, self.mcc_prompt, self.short_delay)
with open(self.filepath, 'w'):
pass
class VexpressBootModule(BootModule):
stage = 'early'
@staticmethod
def probe(target):
return True
def __init__(self, target, uefi_entry=None,
port='/dev/ttyS0', baudrate=115200,
mcc_prompt=DEFAULT_MCC_PROMPT,
timeout=120, short_delay=1):
super(VexpressBootModule, self).__init__(target)
self.port = port
self.baudrate = baudrate
self.uefi_entry = uefi_entry
self.mcc_prompt = mcc_prompt
self.timeout = timeout
self.short_delay = short_delay
def __call__(self):
with open_serial_connection(port=self.port,
baudrate=self.baudrate,
timeout=self.timeout,
init_dtr=0) as tty:
self.get_through_early_boot(tty)
self.perform_boot_sequence(tty)
self.wait_for_android_prompt(tty)
def perform_boot_sequence(self, tty):
raise NotImplementedError()
def get_through_early_boot(self, tty):
self.logger.debug('Establishing initial state...')
tty.sendline('')
i = tty.expect([AUTOSTART_MESSAGE, POWERUP_MESSAGE, self.mcc_prompt])
if i == 2:
self.logger.debug('Saw MCC prompt.')
time.sleep(self.short_delay)
tty.sendline('reboot')
elif i == 1:
self.logger.debug('Saw powering up message (assuming soft reboot).')
else:
self.logger.debug('Saw auto boot message.')
tty.sendline('')
time.sleep(self.short_delay)
tty.sendline('reboot')
def get_uefi_menu(self, tty):
menu = UefiMenu(tty)
self.logger.debug('Waiting for UEFI menu...')
menu.wait(timeout=self.timeout)
return menu
def wait_for_android_prompt(self, tty):
self.logger.debug('Waiting for the Android prompt.')
tty.expect(self.target.shell_prompt, timeout=self.timeout)
# This delay is needed to allow the platform some time to finish
# initilizing; querying the ip address too early from connect() may
# result in a bogus address being assigned to eth0.
time.sleep(5)
class VexpressUefiBoot(VexpressBootModule):
name = 'vexpress-uefi'
def __init__(self, target, uefi_entry,
image, fdt, bootargs, initrd,
*args, **kwargs):
super(VexpressUefiBoot, self).__init__(target, uefi_entry=uefi_entry,
*args, **kwargs)
self.uefi_config = self._create_config(image, fdt, bootargs, initrd)
def perform_boot_sequence(self, tty):
menu = self.get_uefi_menu(tty)
try:
menu.select(self.uefi_entry)
except LookupError:
self.logger.debug('{} UEFI entry not found.'.format(self.uefi_entry))
self.logger.debug('Attempting to create one using default flasher configuration.')
menu.create_entry(self.uefi_entry, self.uefi_config)
menu.select(self.uefi_entry)
def _create_config(self, image, fdt, bootargs, initrd): # pylint: disable=R0201
config_dict = {
'image_name': image,
'image_args': bootargs,
'initrd': initrd,
}
if fdt:
config_dict['fdt_support'] = True
config_dict['fdt_path'] = fdt
else:
config_dict['fdt_support'] = False
return UefiConfig(config_dict)
class VexpressUefiShellBoot(VexpressBootModule):
name = 'vexpress-uefi-shell'
def __init__(self, target, uefi_entry='^Shell$',
efi_shell_prompt='Shell>',
image='kernel', bootargs=None,
*args, **kwargs):
super(VexpressUefiShellBoot, self).__init__(target, uefi_entry=uefi_entry,
*args, **kwargs)
self.efi_shell_prompt = efi_shell_prompt
self.image = image
self.bootargs = bootargs
def perform_boot_sequence(self, tty):
menu = self.get_uefi_menu(tty)
try:
menu.select(self.uefi_entry)
except LookupError:
raise TargetError('Did not see "{}" UEFI entry.'.format(self.uefi_entry))
tty.expect(self.efi_shell_prompt, timeout=self.timeout)
if self.bootargs:
tty.sendline('') # stop default boot
time.sleep(self.short_delay)
efi_shell_command = '{} {}'.format(self.image, self.bootargs)
self.logger.debug(efi_shell_command)
write_characters(tty, efi_shell_command)
tty.sendline('\r\n')
class VexpressUBoot(VexpressBootModule):
name = 'vexpress-u-boot'
def __init__(self, target, env=None,
*args, **kwargs):
super(VexpressUBoot, self).__init__(target, *args, **kwargs)
self.env = env
def perform_boot_sequence(self, tty):
if self.env is None:
return # Will boot automatically
menu = UbootMenu(tty)
self.logger.debug('Waiting for U-Boot prompt...')
menu.open(timeout=120)
for var, value in self.env.iteritems():
menu.setenv(var, value)
menu.boot()
class VexpressBootmon(VexpressBootModule):
name = 'vexpress-bootmon'
def __init__(self, target,
image, fdt, initrd, bootargs,
uses_bootscript=False,
bootmon_prompt='>',
*args, **kwargs):
super(VexpressBootmon, self).__init__(target, *args, **kwargs)
self.image = image
self.fdt = fdt
self.initrd = initrd
self.bootargs = bootargs
self.uses_bootscript = uses_bootscript
self.bootmon_prompt = bootmon_prompt
def perform_boot_sequence(self, tty):
if self.uses_bootscript:
return # Will boot automatically
time.sleep(self.short_delay)
tty.expect(self.bootmon_prompt, timeout=self.timeout)
with open_serial_connection(port=self.port,
baudrate=self.baudrate,
timeout=self.timeout,
init_dtr=0) as tty:
write_characters(tty, 'fl linux fdt {}'.format(self.fdt))
write_characters(tty, 'fl linux initrd {}'.format(self.initrd))
write_characters(tty, 'fl linux boot {} {}'.format(self.image,
self.bootargs))
class VersatileExpressFlashModule(FlashModule):
name = 'vexpress-vemsd'
description = """
Enables flashing of kernels and firmware to ARM Versatile Express devices.
This modules enables flashing of image bundles or individual images to ARM
Versatile Express-based devices (e.g. JUNO) via host-mounted MicroSD on the
board.
The bundle, if specified, must reflect the directory structure of the MicroSD
and will be extracted directly into the location it is mounted on the host. The
images, if specified, must be a dict mapping the absolute path of the image on
the host to the destination path within the board's MicroSD; the destination path
may be either absolute, or relative to the MicroSD mount location.
"""
stage = 'early'
@staticmethod
def probe(target):
if not target.has('hard_reset'):
return False
return True
def __init__(self, target, vemsd_mount, mcc_prompt=DEFAULT_MCC_PROMPT, timeout=30, short_delay=1):
super(VersatileExpressFlashModule, self).__init__(target)
self.vemsd_mount = vemsd_mount
self.mcc_prompt = mcc_prompt
self.timeout = timeout
self.short_delay = short_delay
def __call__(self, image_bundle=None, images=None, bootargs=None):
self.target.hard_reset()
with open_serial_connection(port=self.target.platform.serial_port,
baudrate=self.target.platform.baudrate,
timeout=self.timeout,
init_dtr=0) as tty:
i = tty.expect([self.mcc_prompt, AUTOSTART_MESSAGE])
if i:
tty.sendline('')
wait_for_vemsd(self.vemsd_mount, tty, self.mcc_prompt, self.short_delay)
try:
if image_bundle:
self._deploy_image_bundle(image_bundle)
if images:
self._overlay_images(images)
os.system('sync')
except (IOError, OSError), e:
msg = 'Could not deploy images to {}; got: {}'
raise TargetError(msg.format(self.vemsd_mount, e))
self.target.boot()
self.target.connect(timeout=30)
def _deploy_image_bundle(self, bundle):
self.logger.debug('Validating {}'.format(bundle))
validate_image_bundle(bundle)
self.logger.debug('Extracting {} into {}...'.format(bundle, self.vemsd_mount))
with tarfile.open(bundle) as tar:
tar.extractall(self.vemsd_mount)
def _overlay_images(self, images):
for dest, src in images.iteritems():
dest = os.path.join(self.vemsd_mount, dest)
self.logger.debug('Copying {} to {}'.format(src, dest))
shutil.copy(src, dest)
# utility functions
def validate_image_bundle(bundle):
if not tarfile.is_tarfile(bundle):
raise HostError('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 HostError(msg.format(bundle))
def wait_for_vemsd(vemsd_mount, tty, mcc_prompt=DEFAULT_MCC_PROMPT, short_delay=1, retries=3):
attempts = 1 + retries
path = os.path.join(vemsd_mount, 'config.txt')
if os.path.exists(path):
return
for _ in xrange(attempts):
tty.sendline('') # clear any garbage
tty.expect(mcc_prompt, timeout=short_delay)
tty.sendline('usb_on')
time.sleep(short_delay * 3)
if os.path.exists(path):
return
raise TargetError('Could not mount {}'.format(vemsd_mount))