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:
122
devlib/module/__init__.py
Normal file
122
devlib/module/__init__.py
Normal 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
128
devlib/module/android.py
Normal 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
122
devlib/module/biglittle.py
Normal 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
206
devlib/module/cgroups.py
Normal 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
63
devlib/module/cooling.py
Normal 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
315
devlib/module/cpufreq.py
Normal 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
138
devlib/module/cpuidle.py
Normal 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
35
devlib/module/hotplug.py
Normal 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
140
devlib/module/hwmon.py
Normal 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
386
devlib/module/vexpress.py
Normal 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))
|
||||
|
||||
Reference in New Issue
Block a user