1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-10-29 22:24:51 +00:00

New target description + moving target stuff under "framework"

Changing the way target descriptions work from a static mapping to
something that is dynamically generated and is extensible via plugins.
Also moving core target implementation stuff under "framework".
This commit is contained in:
Sergei Trofimov
2017-03-06 11:10:25 +00:00
parent 18d001fd76
commit 42539bbe0d
43 changed files with 6229 additions and 2586 deletions

View File

View File

@@ -0,0 +1,20 @@
from copy import copy
#Not going to be used for now.
class TargetConfig(dict):
"""
Represents a configuration for a target.
"""
def __init__(self, config=None):
if isinstance(config, TargetConfig):
self.__dict__ = copy(config.__dict__)
elif hasattr(config, 'iteritems'):
for k, v in config.iteritems:
self.set(k, v)
elif config:
raise ValueError(config)
def set(self, name, value):
setattr(self, name, value)

View File

@@ -0,0 +1,252 @@
from collections import OrderedDict
from copy import copy
from devlib import (LinuxTarget, AndroidTarget, LocalLinuxTarget,
Platform, Juno, TC2, Gem5SimulationPlatform)
from wa.framework import pluginloader
from wa.framework.exception import PluginLoaderError
from wa.framework.plugin import Plugin, Parameter
from wa.utils.types import list_of_strings, list_of_ints
def get_target_descriptions(loader=pluginloader):
targets = {}
for cls in loader.list_target_descriptors():
descriptor = cls()
for desc in descriptor.get_descriptions():
if desc.name in targets:
msg = 'Duplicate target "{}" returned by {} and {}'
prev_dtor = targets[desc.name].source
raise PluginLoaderError(msg.format(dsc.name, prev_dtor.name,
descriptor.name))
targets[desc.name] = desc
return targets.values()
class TargetDescription(object):
def __init__(self, name, source, description=None, target=None, platform=None,
conn=None, target_params=None, platform_params=None,
conn_params=None):
self.name = name
self.source = source
self.description = description
self.target = target
self.platform = platform
self.connection = conn
self._set('target_params', target_params)
self._set('platform_params', platform_params)
self._set('conn_params', conn_params)
def _set(self, attr, vals):
if vals is None:
vals = {}
elif isiterable(vals):
if not hasattr(vals, 'iteritems'):
vals = {v.name: v for v in vals}
else:
msg = '{} must be iterable; got "{}"'
raise ValueError(msg.format(attr, vals))
setattr(self, attr, vals)
class TargetDescriptor(Plugin):
kind = 'target_descriptor'
def get_descriptions(self):
return []
COMMON_TARGET_PARAMS = [
Parameter('working_directory', kind=str,
description='''
On-target working directory that will be used by WA. This
directory must be writable by the user WA logs in as without
the need for privilege elevation.
'''),
Parameter('executables_directory', kind=str,
description='''
On-target directory where WA will install its executable
binaries. This location must allow execution. This location does
*not* need to be writable by unprivileged users or rooted devices
(WA will install with elevated privileges as necessary).
'''),
Parameter('modules', kind=list_of_strings,
description='''
A list of additional modules to be installed for the target.
``devlib`` implements functionality for particular subsystems as
modules. A number of "default" modules (e.g. for cpufreq
subsystem) are loaded automatically, unless explicitly disabled.
If additional modules need to be loaded, they may be specified
using this parameter.
Please see ``devlab`` documentation for information on the available
modules.
'''),
]
COMMON_PLATFORM_PARAMS = [
Parameter('core_names', kind=list_of_strings,
description='''
List of names of CPU cores in the order that they appear to the
kernel. If not specified, it will be inferred from the platform.
'''),
Parameter('core_clusters', kind=list_of_ints,
description='''
Cluster mapping corresponding to the cores in ``core_names``.
Cluster indexing starts at ``0``. If not specified, this will be
inferred from ``core_names`` -- consecutive cores with the same
name will be assumed to share a cluster.
'''),
Parameter('big_core', kind=str,
description='''
The name of the big cores in a big.LITTLE system. If not
specified, this will be inferred, either from the name (if one of
the names in ``core_names`` matches known big cores), or by
assuming that the last cluster is big.
'''),
Parameter('model', kind=str,
description='''
Hardware model of the platform. If not specified, an attempt will
be made to read it from target.
'''),
Parameter('modules', kind=list_of_strings,
description='''
An additional list of modules to be loaded into the target.
'''),
]
VEXPRESS_PLATFORM_PARAMS = [
Parameter('serial_port', kind=str,
description='''
The serial device/port on the host for the initial connection to
the target (used for early boot, flashing, etc).
'''),
Parameter('baudrate', kind=int,
description='''
Baud rate for the serial connection.
'''),
Parameter('vemsd_mount', kind=str,
description='''
VExpress MicroSD card mount location. This is a MicroSD card in
the VExpress device that is mounted on the host via USB. The card
contains configuration files for the platform and firmware and
kernel images to be flashed.
'''),
Parameter('bootloader', kind=str,
allowed_values=['uefi', 'uefi-shell', 'u-boot', 'bootmon'],
description='''
Selects the bootloader mechanism used by the board. Depending on
firmware version, a number of possible boot mechanisms may be use.
Please see ``devlib`` documentation for descriptions.
'''),
Parameter('hard_reset_method', kind=str,
allowed_values=['dtr', 'reboottxt'],
description='''
There are a couple of ways to reset VersatileExpress board if the
software running on the board becomes unresponsive. Both require
configuration to be enabled (please see ``devlib`` documentation).
``dtr``: toggle the DTR line on the serial connection
``reboottxt``: create ``reboot.txt`` in the root of the VEMSD mount.
'''),
]
GEM5_PLATFORM_PARAMS = [
Parameter('host_output_dir', kind=str, mandatory=True,
description='''
Path on the host where gem5 output (e.g. stats file) will be placed.
'''),
Parameter('gem5_bin', kind=str, mandatory=True,
description='''
Path to the gem5 binary
'''),
Parameter('gem5_args', kind=str, mandatory=True,
description='''
Arguments to be passed to the gem5 binary
'''),
Parameter('gem5_virtio', kind=str, mandatory=True,
description='''
VirtIO device setup arguments to be passed to gem5. VirtIO is used
to transfer files between the simulation and the host.
'''),
]
# name --> (target_class, params_list, defaults)
TARGETS = {
'linux': (LinuxTarget, COMMON_TARGET_PARAMS, None),
'android': (AndroidTarget, COMMON_TARGET_PARAMS +
[Parameter('package_data_directory', kind=str, default='/data/data',
description='''
Directory containing Android data
'''),
], None),
'local': (LocalLinuxTarget, COMMON_TARGET_PARAMS, None),
}
# name --> (platform_class, params_list, defaults)
PLATFORMS = {
'generic': (Platform, COMMON_PLATFORM_PARAMS, None),
'juno': (Juno, COMMON_PLATFORM_PARAMS + VEXPRESS_PLATFORM_PARAMS,
{
'vemsd_mount': '/media/JUNO',
'baudrate': 115200,
'bootloader': 'u-boot',
'hard_reset_method': 'dtr',
}),
'tc2': (TC2, COMMON_PLATFORM_PARAMS + VEXPRESS_PLATFORM_PARAMS,
{
'vemsd_mount': '/media/VEMSD',
'baudrate': 38400,
'bootloader': 'bootmon',
'hard_reset_method': 'reboottxt',
}),
'gem5': (Gem5SimulationPlatform, GEM5_PLATFORM_PARAMS, None),
}
class DefaultTargetDescriptor(TargetDescriptor):
name = 'devlib_targets'
description = """
The default target descriptor that provides descriptions in the form
<platform>_<target>.
These map directly onto ``Target``\ s and ``Platform``\ s supplied by ``devlib``.
"""
def get_descriptions(self):
result = []
for target_name, target_tuple in TARGETS.iteritems():
target, target_params = self._get_item(target_tuple)
for platform_name, platform_tuple in PLATFORMS.iteritems():
platform, platform_params = self._get_item(platform_tuple)
name = '{}_{}'.format(platform_name, target_name)
td = TargetDescription(name, self)
td.target = target
td.platform = platform
td.target_params = target_params
td.platform_params = platform_params
result.append(td)
return result
def _get_item(self, item_tuple):
cls, params, defaults = item_tuple
if not defaults:
return cls, params
param_map = OrderedDict((p.name, copy(p)) for p in params)
for name, value in defaults.iteritems():
if name not in param_map:
raise ValueError('Unexpected default "{}"'.format(name))
param_map[name].default = value
return cls, param_map.values()

View File

@@ -0,0 +1,78 @@
from devlib import AndroidTarget
from devlib.exception import TargetError
from devlib.target import KernelConfig, KernelVersion, Cpuinfo
class TargetInfo(object):
@staticmethod
def from_pod(pod):
instance = TargetInfo()
instance.target = pod['target']
instance.abi = pod['abi']
instance.cpuinfo = Cpuinfo(pod['cpuinfo'])
instance.os = pod['os']
instance.os_version = pod['os_version']
instance.abi = pod['abi']
instance.is_rooted = pod['is_rooted']
instance.kernel_version = KernelVersion(pod['kernel_release'],
pod['kernel_version'])
instance.kernel_config = KernelConfig(pod['kernel_config'])
if pod["target"] == "AndroidTarget":
instance.screen_resolution = pod['screen_resolution']
instance.prop = pod['prop']
instance.prop = pod['android_id']
return instance
def __init__(self, target=None):
if target:
self.target = target.__class__.__name__
self.cpuinfo = target.cpuinfo
self.os = target.os
self.os_version = target.os_version
self.abi = target.abi
self.is_rooted = target.is_rooted
self.kernel_version = target.kernel_version
self.kernel_config = target.config
if isinstance(target, AndroidTarget):
self.screen_resolution = target.screen_resolution
self.prop = target.getprop()
self.android_id = target.android_id
else:
self.target = None
self.cpuinfo = None
self.os = None
self.os_version = None
self.abi = None
self.is_rooted = None
self.kernel_version = None
self.kernel_config = None
if isinstance(target, AndroidTarget):
self.screen_resolution = None
self.prop = None
self.android_id = None
def to_pod(self):
pod = {}
pod['target'] = self.target
pod['abi'] = self.abi
pod['cpuinfo'] = self.cpuinfo.sections
pod['os'] = self.os
pod['os_version'] = self.os_version
pod['abi'] = self.abi
pod['is_rooted'] = self.is_rooted
pod['kernel_release'] = self.kernel_version.release
pod['kernel_version'] = self.kernel_version.version
pod['kernel_config'] = dict(self.kernel_config.iteritems())
if self.target == "AndroidTarget":
pod['screen_resolution'] = self.screen_resolution
pod['prop'] = self.prop
pod['android_id'] = self.android_id
return pod

View File

@@ -0,0 +1,383 @@
import logging
import tempfile
import threading
import os
import time
import shutil
import sys
from wa.framework import signal
from wa.framework.exception import WorkerThreadError, ConfigError
from wa.framework.plugin import Parameter
from wa.framework.target.info import TargetInfo
from wa.framework.target.runtime_config import (SysfileValuesRuntimeConfig,
HotplugRuntimeConfig,
CpufreqRuntimeConfig,
CpuidleRuntimeConfig)
from wa.utils.misc import isiterable
from wa.utils.serializer import json
from devlib import LocalLinuxTarget, LinuxTarget, AndroidTarget
from devlib.utils.types import identifier
# from wa.target.manager import AndroidTargetManager, LinuxTargetManager
class TargetManager(object):
name = 'target-manager'
description = """
Instanciated the required target and performs configuration and validation
of the device.
"""
parameters = [
Parameter('disconnect', kind=bool, default=False,
description="""
Specifies whether the target should be disconnected from
at the end of the run.
"""),
]
DEVICE_MAPPING = {'test' : {'platform_name':'generic',
'target_name': 'android'},
'other': {'platform_name':'test',
'target_name': 'linux'},
}
runtime_config_cls = [
# order matters
SysfileValuesRuntimeConfig,
HotplugRuntimeConfig,
CpufreqRuntimeConfig,
CpuidleRuntimeConfig,
]
def __init__(self, name, parameters):
self.name = name
self.target = None
self.assistant = None
self.target_name = None
self.platform_name = None
self.parameters = parameters
self.disconnect = parameters.get('disconnect')
self.info = TargetInfo()
# Determine platform and target based on passed name
self._parse_name()
# Create target
self._get_target()
# Create an assistant to perform target specific configuration
self._get_assistant()
### HERE FOR TESTING, WILL BE CALLED EXTERNALLY ###
# Connect to device and retrieve details.
# self.initialize()
# self.add_parameters()
# self.validate_parameters()
# self.set_parameters()
def initialize(self):
self.runtime_configs = [cls(self.target) for cls in self.runtime_config_cls]
# if self.parameters:
# self.logger.info('Connecting to the device')
with signal.wrap('TARGET_CONNECT'):
self.target.connect()
# self.info.load(self.target)
# info_file = os.path.join(self.context.info_directory, 'target.json')
# with open(info_file, 'w') as wfh:
# json.dump(self.info.to_pod(), wfh)
def finalize(self):
# self.logger.info('Disconnecting from the device')
if self.disconnect:
with signal.wrap('TARGET_DISCONNECT'):
self.target.disconnect()
def add_parameters(self, parameters=None):
if parameters:
self.parameters = parameters
if not self.parameters:
raise ConfigError('No Configuration Provided')
for name in self.parameters.keys():
for cfg in self.runtime_configs:
# if name in cfg.supported_parameters:
if any(parameter in name for parameter in cfg.supported_parameters):
cfg.add(name, self.parameters.pop(name))
def validate_parameters(self):
for cfg in self.runtime_configs:
cfg.validate()
def set_parameters(self):
for cfg in self.runtime_configs:
cfg.set()
def clear_parameters(self):
for cfg in self.runtime_configs:
cfg.clear()
def _parse_name(self):
# Try and get platform and target
self.name = identifier(self.name.replace('-', '_'))
if '_' in self.name:
self.platform_name, self.target_name = self.name.split('_', 1)
elif self.name in self.DEVICE_MAPPING:
self.platform_name = self.DEVICE_MAPPING[self.name]['platform_name']
self.target_name = self.DEVICE_MAPPING[self.name]['target_name']
else:
raise ConfigError('Unknown Device Specified {}'.format(self.name))
def _get_target(self):
# Create a corresponding target and target-assistant
if self.target_name == 'android':
self.target = AndroidTarget()
elif self.target_name == 'linux':
self.target = LinuxTarget() # pylint: disable=redefined-variable-type
elif self.target_name == 'localLinux':
self.target = LocalLinuxTarget()
else:
raise ConfigError('Unknown Target Specified {}'.format(self.target_name))
def _get_assistant(self):
# Create a corresponding target and target-assistant to help with platformy stuff?
if self.target_name == 'android':
self.assistant = AndroidAssistant(self.target)
elif self.target_name in ['linux', 'localLinux']:
self.assistant = LinuxAssistant(self.target) # pylint: disable=redefined-variable-type
else:
raise ConfigError('Unknown Target Specified {}'.format(self.target_name))
# def validate_runtime_parameters(self, parameters):
# for name, value in parameters.iteritems():
# self.add_parameter(name, value)
# self.validate_parameters()
# def set_runtime_parameters(self, parameters):
# # self.clear()
# for name, value in parameters.iteritems():
# self.add_parameter(name, value)
# self.set_parameters()
class LinuxAssistant(object):
name = 'linux-assistant'
description = """
Performs configuration, instrumentation, etc. during runs on Linux targets.
"""
def __init__(self, target, **kwargs):
self.target = target
# parameters = [
# Parameter('disconnect', kind=bool, default=False,
# description="""
# Specifies whether the target should be disconnected from
# at the end of the run.
# """),
# ]
# runtime_config_cls = [
# # order matters
# SysfileValuesRuntimeConfig,
# HotplugRuntimeConfig,
# CpufreqRuntimeConfig,
# CpuidleRuntimeConfig,
# ]
# def __init__(self, target, context, **kwargs):
# # super(LinuxTargetManager, self).__init__(target, context, **kwargs)
# self.target = target
# self.context = context
# self.info = TargetInfo()
# self.runtime_configs = [cls(target) for cls in self.runtime_config_cls]
# def __init__(self):
# # super(LinuxTargetManager, self).__init__(target, context, **kwargs)
# self.target = target
# self.info = TargetInfo()
# self.parameters = parameters
# self.info = TargetInfo()
# self.runtime_configs = [cls(target) for cls in self.runtime_config_cls]
# def initialize(self):
# # self.runtime_configs = [cls(self.target) for cls in self.runtime_config_cls]
# # if self.parameters:
# self.logger.info('Connecting to the device')
# with signal.wrap('TARGET_CONNECT'):
# self.target.connect()
# self.info.load(self.target)
# # info_file = os.path.join(self.context.info_directory, 'target.json')
# # with open(info_file, 'w') as wfh:
# # json.dump(self.info.to_pod(), wfh)
# def finalize(self, runner):
# self.logger.info('Disconnecting from the device')
# if self.disconnect:
# with signal.wrap('TARGET_DISCONNECT'):
# self.target.disconnect()
# def _add_parameters(self):
# for name, value in self.parameters.iteritems():
# self.add_parameter(name, value)
# def validate_runtime_parameters(self, parameters):
# self.clear()
# for name, value in parameters.iteritems():
# self.add_parameter(name, value)
# self.validate_parameters()
# def set_runtime_parameters(self, parameters):
# self.clear()
# for name, value in parameters.iteritems():
# self.add_parameter(name, value)
# self.set_parameters()
# def clear_parameters(self):
# for cfg in self.runtime_configs:
# cfg.clear()
# def add_parameter(self, name, value):
# for cfg in self.runtime_configs:
# if name in cfg.supported_parameters:
# cfg.add(name, value)
# return
# raise ConfigError('Unexpected runtime parameter "{}".'.format(name))
# def validate_parameters(self):
# for cfg in self.runtime_configs:
# cfg.validate()
# def set_parameters(self):
# for cfg in self.runtime_configs:
# cfg.set()
class AndroidAssistant(LinuxAssistant):
name = 'android-assistant'
description = """
Extends ``LinuxTargetManager`` with Android-specific operations.
"""
parameters = [
Parameter('logcat_poll_period', kind=int,
description="""
If specified, logcat will cached in a temporary file on the
host every ``logcat_poll_period`` seconds. This is useful for
longer job executions, where on-device logcat buffer may not be
big enough to capture output for the entire execution.
"""),
]
def __init__(self, target, **kwargs):
super(AndroidAssistant, self).__init__(target)
self.logcat_poll_period = kwargs.get('logcat_poll_period', None)
if self.logcat_poll_period:
self.logcat_poller = LogcatPoller(target, self.logcat_poll_period)
else:
self.logcat_poller = None
# def __init__(self, target, context, **kwargs):
# super(AndroidAssistant, self).__init__(target, context, **kwargs)
# self.logcat_poll_period = kwargs.get('logcat_poll_period', None)
# if self.logcat_poll_period:
# self.logcat_poller = LogcatPoller(target, self.logcat_poll_period)
# else:
# self.logcat_poller = None
# def next_job(self, job):
# super(AndroidAssistant, self).next_job(job)
# if self.logcat_poller:
# self.logcat_poller.start()
# def job_done(self, job):
# super(AndroidAssistant, self).job_done(job)
# if self.logcat_poller:
# self.logcat_poller.stop()
# outfile = os.path.join(self.context.output_directory, 'logcat.log')
# self.logger.debug('Dumping logcat to {}'.format(outfile))
# self.dump_logcat(outfile)
# self.clear()
def dump_logcat(self, outfile):
if self.logcat_poller:
self.logcat_poller.write_log(outfile)
else:
self.target.dump_logcat(outfile)
def clear_logcat(self):
if self.logcat_poller:
self.logcat_poller.clear_buffer()
class LogcatPoller(threading.Thread):
def __init__(self, target, period=60, timeout=30):
super(LogcatPoller, self).__init__()
self.target = target
self.logger = logging.getLogger('logcat')
self.period = period
self.timeout = timeout
self.stop_signal = threading.Event()
self.lock = threading.Lock()
self.buffer_file = tempfile.mktemp()
self.last_poll = 0
self.daemon = True
self.exc = None
def start(self):
self.logger.debug('starting polling')
try:
while True:
if self.stop_signal.is_set():
break
with self.lock:
current_time = time.time()
if (current_time - self.last_poll) >= self.period:
self.poll()
time.sleep(0.5)
except Exception: # pylint: disable=W0703
self.exc = WorkerThreadError(self.name, sys.exc_info())
self.logger.debug('polling stopped')
def stop(self):
self.logger.debug('Stopping logcat polling')
self.stop_signal.set()
self.join(self.timeout)
if self.is_alive():
self.logger.error('Could not join logcat poller thread.')
if self.exc:
raise self.exc # pylint: disable=E0702
def clear_buffer(self):
self.logger.debug('clearing logcat buffer')
with self.lock:
self.target.clear_logcat()
with open(self.buffer_file, 'w') as _: # NOQA
pass
def write_log(self, outfile):
with self.lock:
self.poll()
if os.path.isfile(self.buffer_file):
shutil.copy(self.buffer_file, outfile)
else: # there was no logcat trace at this time
with open(outfile, 'w') as _: # NOQA
pass
def close(self):
self.logger.debug('closing poller')
if os.path.isfile(self.buffer_file):
os.remove(self.buffer_file)
def poll(self):
self.last_poll = time.time()
self.target.dump_logcat(self.buffer_file, append=True, timeout=self.timeout)
self.target.clear_logcat()

View File

@@ -0,0 +1,454 @@
from collections import defaultdict, OrderedDict
from wa.framework.plugin import Plugin
from wa.framework.exception import ConfigError
from devlib.exception import TargetError
from devlib.utils.misc import unique
from devlib.utils.types import integer
class RuntimeConfig(Plugin):
kind = 'runtime-config'
parameters = [
]
# class RuntimeConfig(object):
@property
def supported_parameters(self):
raise NotImplementedError()
@property
def core_names(self):
return unique(self.target.core_names)
def __init__(self, target):
super(RuntimeConfig, self).__init__()
self.target = target
def initialize(self, context):
pass
def add(self, name, value):
raise NotImplementedError()
def validate(self):
return True
def set(self):
raise NotImplementedError()
def clear(self):
raise NotImplementedError()
class HotplugRuntimeConfig(RuntimeConfig):
##### NOTE: Currently if initialized with cores hotplugged, this will fail when trying to hotplug back in
@property
def supported_parameters(self):
params = ['cores']
return params
def __init__(self, target):
super(HotplugRuntimeConfig, self).__init__(target)
self.num_cores = defaultdict(dict)
def add(self, name, value):
if not self.target.has('hotplug'):
raise TargetError('Target does not support hotplug.')
core, _ = split_parameter_name(name, self.supported_parameters)
# cpus = cpusFromPrefix(core, self.target)
# core = name.split('_')[0]
value = integer(value)
if core not in self.core_names:
raise ValueError(name)
max_cores = self.core_count(core)
if value > max_cores:
message = 'Cannot set number of {}\'s to {}; max is {}'
raise ValueError(message.format(core, value, max_cores))
self.num_cores[core] = value
if all(v == 0 for v in self.num_cores.values()):
raise ValueError('Cannot set number of all cores to 0')
def set(self):
for c, n in reversed(sorted(self.num_cores.iteritems(),
key=lambda x: x[1])):
self.set_num_online_cpus(c, n)
def clear(self):
self.num_cores = defaultdict(dict)
def set_num_online_cpus(self, core, number):
indexes = [i for i, c in enumerate(self.target.core_names) if c == core]
self.target.hotplug.online(*indexes[:number])
self.target.hotplug.offline(*indexes[number:])
def core_count(self, core):
return sum(1 for c in self.target.core_names if c == core)
class SysfileValuesRuntimeConfig(RuntimeConfig):
@property
def supported_parameters(self):
return ['sysfile_values']
def __init__(self, target):
super(SysfileValuesRuntimeConfig, self).__init__(target)
self.sysfile_values = OrderedDict()
def add(self, name, value):
for f, v in value.iteritems():
if f.endswith('+'):
f = f[:-1]
elif f.endswith('+!'):
f = f[:-2] + '!'
else:
if f.endswith('!'):
self._check_exists(f[:-1])
else:
self._check_exists(f)
self.sysfile_values[f] = v
def set(self):
for f, v in self.sysfile_values.iteritems():
verify = True
if f.endswith('!'):
verify = False
f = f[:-1]
self.target.write_value(f, v, verify=verify)
def clear(self):
self.sysfile_values = OrderedDict()
def _check_exists(self, path):
if not self.target.file_exists(path):
raise ConfigError('Sysfile "{}" does not exist.'.format(path))
class CpufreqRuntimeConfig(RuntimeConfig):
@property
def supported_parameters(self):
params = ['frequency']
params.extend(['max_frequency'])
params.extend(['min_frequency'])
params.extend(['governor'])
params.extend(['governor_tunables'])
return params
def __init__(self, target):
super(CpufreqRuntimeConfig, self).__init__(target)
self.config = defaultdict(dict)
self.supports_userspace = None
self.supported_freqs = {}
self.supported_govenors = {}
self.min_supported_freq = {}
self.max_supported_freq = {}
for cpu in self.target.list_online_cpus():
self.supported_freqs[cpu] = self.target.cpufreq.list_frequencies(cpu) or []
self.supported_govenors[cpu] = self.target.cpufreq.list_governors(cpu) or []
def add(self, name, value):
if not self.target.has('cpufreq'):
raise TargetError('Target does not support cpufreq.')
prefix, parameter = split_parameter_name(name, self.supported_parameters)
# Get list of valid cpus for a given prefix.
cpus = uniqueDomainCpusFromPrefix(prefix, self.target)
for cpu in cpus:
# if cpu not in self.target.list_online_cpus():
# message = 'Unexpected core name "{}"; must be in {}'
# raise ConfigError(message.format(core, self.core_names))
# try:
# cpu = self.target.list_online_cpus(core)[0]
# except IndexError:
# message = 'Cannot retrieve frequencies for {} as no CPUs are online.'
# raise TargetError(message.format(core))
if parameter.endswith('frequency'):
try:
value = integer(value)
except ValueError:
if value.upper() == 'MAX':
value = self.supported_freqs[cpu][-1]
elif value.upper() == 'MIN':
value = self.supported_freqs[cpu][0]
else:
msg = 'Invalid value {} specified for {}'
raise ConfigError(msg.format(value, parameter))
self.config[cpu][parameter] = value
def set(self):
for cpu in self.config:
config = self.config[cpu]
if config.get('governor'):
self.configure_governor(cpu,
config.get('governor'),
config.get('governor_tunables'))
self.configure_frequency(cpu,
config.get('frequency'),
config.get('min_frequency'),
config.get('max_frequency'))
def clear(self):
self.config = defaultdict(dict)
def validate(self):
for cpu in self.config:
if cpu not in self.target.list_online_cpus():
message = 'Cannot configure frequencies for {} as no CPUs are online.'
raise TargetError(message.format(cpu))
config = self.config[cpu]
minf = config.get('min_frequency')
maxf = config.get('max_frequency')
freq = config.get('frequency')
governor = config.get('governor')
governor_tunables = config.get('governor_tunables')
if maxf and minf > maxf:
message = '{}: min_frequency ({}) cannot be greater than max_frequency ({})'
raise ConfigError(message.format(cpu, minf, maxf))
if maxf and freq > maxf:
message = '{}: cpu frequency ({}) cannot be greater than max_frequency ({})'
raise ConfigError(message.format(cpu, freq, maxf))
if freq and minf > freq:
message = '{}: min_frequency ({}) cannot be greater than cpu frequency ({})'
raise ConfigError(message.format(cpu, minf, freq))
# Check that either userspace governor is available or min and max do not differ to frequency
if 'userspace' not in self.supported_govenors[cpu]:
self.supports_userspace = False
if minf and minf != freq:
message = '{}: "userspace" governor not available, min frequency ({}) cannot be different to frequency {}'
raise ConfigError(message.format(cpu, minf, freq))
if maxf and maxf != freq:
message = '{}: "userspace" governor not available, max frequency ({}) cannot be different to frequency {}'
raise ConfigError(message.format(cpu, maxf, freq))
else:
self.supports_userspace = True
# Check that specified values are available on the cpu
if minf and not minf in self.supported_freqs[cpu]:
msg = '{}: Minimum frequency {}Hz not available. Must be in {}'.format(cpu, minf, self.supported_freqs[cpu])
raise TargetError(msg)
if maxf and not maxf in self.supported_freqs[cpu]:
msg = '{}: Maximum frequency {}Hz not available. Must be in {}'.format(cpu, maxf, self.supported_freqs[cpu])
raise TargetError(msg)
if freq and not freq in self.supported_freqs[cpu]:
msg = '{}: Frequency {}Hz not available. Must be in {}'.format(cpu, freq, self.supported_freqs[cpu])
raise TargetError(msg)
if governor and governor not in self.supported_govenors[cpu]:
raise TargetError('{}: {} governor not available'.format(cpu, governor))
if governor_tunables and not governor:
raise TargetError('{}: {} governor tunables cannot be provided without a governor'.format(cpu, governor))
def configure_frequency(self, cpu, freq=None, min_freq=None, max_freq=None):
if cpu not in self.target.list_online_cpus():
message = 'Cannot configure frequencies for {} as no CPUs are online.'
raise TargetError(message.format(cpu))
current_min_freq = self.target.cpufreq.get_min_frequency(cpu)
current_freq = self.target.cpufreq.get_frequency(cpu)
current_max_freq = self.target.cpufreq.get_max_frequency(cpu)
if freq:
# If 'userspace' governor is not available 'spoof' functionality
if not self.supports_userspace:
min_freq = max_freq = freq
# else: # Find better alternative for this.
# Set min/max frequency if required
# if not min_freq:
# min_freq = self.target.cpufreq.get_min_frequency(cpu)
# if not max_freq:
# max_freq = self.target.cpufreq.get_max_frequency(cpu)
if freq < current_freq:
self.target.cpufreq.set_min_frequency(cpu, min_freq)
if self.supports_userspace:
self.target.cpufreq.set_frequency(cpu, freq)
self.target.cpufreq.set_max_frequency(cpu, max_freq)
else:
self.target.cpufreq.set_max_frequency(cpu, max_freq)
if self.supports_userspace:
self.target.cpufreq.set_frequency(cpu, freq)
self.target.cpufreq.set_min_frequency(cpu, min_freq)
return
if max_freq:
if max_freq < current_min_freq:
if min_freq:
self.target.cpufreq.set_min_frequency(cpu, min_freq)
self.target.cpufreq.set_max_frequency(cpu, max_freq)
min_freq_set = True
else:
message = '{}: Cannot set max_frequency ({}) below current min frequency ({}).'
raise TargetError(message.format(cpu, max_freq, current_min_freq))
else:
self.target.cpufreq.set_max_frequency(cpu, max_freq)
if min_freq and not min_freq_set:
current_max_freq = max_freq or current_max_freq
if min_freq > current_max_freq:
message = '{}: Cannot set min_frequency ({}) below current max frequency ({}).'
raise TargetError(message.format(cpu, max_freq, current_min_freq))
self.target.cpufreq.set_min_frequency(cpu, min_freq)
def configure_governor(self, cpu, governor, governor_tunables=None):
if cpu not in self.target.list_online_cpus():
message = 'Cannot configure governor for {} as no CPUs are online.'
raise TargetError(message.format(cpu))
# for cpu in self.target.list_online_cpus(cpu): #All cpus or only online?
if governor not in self.supported_govenors[cpu]:
raise TargetError('{}: {} governor not available'.format(cpu, governor))
if governor_tunables:
self.target.cpufreq.set_governor(cpu, governor, **governor_tunables)
else:
self.target.cpufreq.set_governor(cpu, governor)
class CpuidleRuntimeConfig(RuntimeConfig):
@property
def supported_parameters(self):
params = ['idle_states']
return params
def __init__(self, target):
super(CpuidleRuntimeConfig, self).__init__(target)
self.config = defaultdict(dict)
self.aliases = ['ENABLE_ALL', 'DISABLE_ALL']
self.available_states = {}
for cpu in self.target.list_online_cpus():
self.available_states[cpu] = self.target.cpuidle.get_states(cpu) or []
def add(self, name, values):
if not self.target.has('cpufreq'):
raise TargetError('Target does not support cpufreq.')
prefix, _ = split_parameter_name(name, self.supported_parameters)
cpus = uniqueDomainCpusFromPrefix(prefix, self.target)
for cpu in cpus:
if values in self.aliases:
self.config[cpu] = [values]
else:
self.config[cpu] = values
def validate(self):
for cpu in self.config:
if cpu not in self.target.list_online_cpus():
message = 'Cannot configure idle states for {} as no CPUs are online.'
raise TargetError(message.format(cpu))
for state in self.config[cpu]:
state = state[1:] if state.startswith('~') else state
# self.available_states.extend(self.aliases)
if state not in self.available_states[cpu] + self.aliases:
message = 'Unexpected idle state "{}"; must be in {}'
raise ConfigError(message.format(state, self.available_states))
def clear(self):
self.config = defaultdict(dict)
def set(self):
for cpu in self.config:
for state in self.config[cpu]:
self.configure_idle_state(state, cpu)
def configure_idle_state(self, state, cpu=None):
if cpu is not None:
if cpu not in self.target.list_online_cpus():
message = 'Cannot configure idle state for {} as no CPUs are online {}.'
raise TargetError(message.format(self.target.core_names[cpu], self.target.list_online_cpus()))
else:
cpu = 0
# Check for aliases
if state == 'ENABLE_ALL':
self.target.cpuidle.enable_all(cpu)
elif state == 'DISABLE_ALL':
self.target.cpuidle.disable_all(cpu)
elif state.startswith('~'):
self.target.cpuidle.disable(state[1:], cpu)
else:
self.target.cpuidle.enable(state, cpu)
# TO BE MOVED TO UTILS FILE
import re
# Function to return the cpu prefix without the trailing underscore if
# present from a given list of parameters, and its matching parameter
def split_parameter_name(name, params):
for param in sorted(params, key=len)[::-1]: # Try matching longest parameter first
if len(name.split(param)) > 1:
prefix, _ = name.split(param)
return prefix[:-1], param
message = 'Cannot split {}, must in the form [core_]parameter'
raise ConfigError(message.format(name))
def cpusFromPrefix(prefix, target):
# Deal with big little substitution
if prefix.lower() == 'big':
prefix = target.big_core
if not prefix:
raise ConfigError('big core name could not be retrieved')
elif prefix.lower() == 'little':
prefix = target.little_core
if not prefix:
raise ConfigError('little core name could not be retrieved')
cpu_list = target.list_online_cpus() + target.list_offline_cpus()
# Apply to all cpus
if not prefix:
cpus = cpu_list
# Return all cores with specified name
elif prefix in target.core_names:
cpus = target.core_cpus(prefix)
# Check if core number has been supplied.
else:
# core_no = prefix[4]
core_no = re.match('cpu([0-9]+)', prefix, re.IGNORECASE)
if core_no:
cpus = [int(core_no.group(1))]
if cpus[0] not in cpu_list:
message = 'CPU{} is not available, must be in {}'
raise ConfigError(message.format(cpus[0], cpu_list))
else:
message = 'Unexpected core name "{}"'
raise ConfigError(message.format(prefix))
# Should this be applied for everything or just all cpus?
# Make sure not to include any cpus within the same frequency domain
# for cpu in cpus:
# if cpu not in cpus: # Already removed
# continue
# cpus = [c for c in cpus if (c is cpu) or
# (c not in target.cpufreq.get_domain_cpus(cpu))]
# print 'Final results ' + str(cpus)
# return cpus
return cpus
# Function to only return cpus list on different frequency domains.
def uniqueDomainCpusFromPrefix(prefix, target):
cpus = cpusFromPrefix(prefix, target)
for cpu in cpus:
if cpu not in cpus: # Already removed
continue
cpus = [c for c in cpus if (c is cpu) or
(c not in target.cpufreq.get_domain_cpus(cpu))]
return cpus