1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2024-10-06 19:01:15 +01:00
workload-automation/wa/framework/target/descriptor.py

624 lines
24 KiB
Python
Raw Normal View History

# Copyright 2018 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 inspect
from collections import OrderedDict
from copy import copy
from devlib import (LinuxTarget, AndroidTarget, LocalLinuxTarget,
ChromeOsTarget, Platform, Juno, TC2, Gem5SimulationPlatform,
AdbConnection, SshConnection, LocalConnection,
Gem5Connection)
from devlib.target import DEFAULT_SHELL_PROMPT
from wa.framework import pluginloader
from wa.framework.configuration.core import get_config_point_map
from wa.framework.exception import PluginLoaderError
from wa.framework.plugin import Plugin, Parameter
from wa.framework.target.assistant import LinuxAssistant, AndroidAssistant, ChromeOsAssistant
from wa.utils.types import list_of_strings, list_of_ints, regex, identifier, caseless_string
from wa.utils.misc import isiterable
def list_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(desc.name, prev_dtor.name,
descriptor.name))
targets[desc.name] = desc
return list(targets.values())
def get_target_description(name, loader=pluginloader):
for tdesc in list_target_descriptions(loader):
if tdesc.name == name:
return tdesc
raise ValueError('Could not find target descriptor "{}"'.format(name))
def instantiate_target(tdesc, params, connect=None, extra_platform_params=None):
# pylint: disable=too-many-locals,too-many-branches
target_params = get_config_point_map(tdesc.target_params)
platform_params = get_config_point_map(tdesc.platform_params)
conn_params = get_config_point_map(tdesc.conn_params)
assistant_params = get_config_point_map(tdesc.assistant_params)
tp, pp, cp = {}, {}, {}
for supported_params, new_params in (target_params, tp), (platform_params, pp), (conn_params, cp):
for name, value in supported_params.items():
if value.default and name == value.name:
new_params[name] = value.default
for name, value in params.items():
if name in target_params:
tp[name] = value
elif name in platform_params:
pp[name] = value
elif name in conn_params:
cp[name] = value
elif name in assistant_params:
pass
else:
msg = 'Unexpected parameter for {}: {}'
raise ValueError(msg.format(tdesc.name, name))
for pname, pval in (extra_platform_params or {}).items():
if pname in pp:
raise RuntimeError('Platform parameter clash: {}'.format(pname))
pp[pname] = pval
tp['platform'] = (tdesc.platform or Platform)(**pp)
if cp:
tp['connection_settings'] = cp
if tdesc.connection:
tp['conn_cls'] = tdesc.connection
if connect is not None:
tp['connect'] = connect
return tdesc.target(**tp)
def instantiate_assistant(tdesc, params, target):
assistant_params = {}
for param in tdesc.assistant_params:
if param.name in params:
assistant_params[param.name] = params[param.name]
elif param.default:
assistant_params[param.name] = param.default
return tdesc.assistant(target, **assistant_params)
class TargetDescription(object):
2017-10-04 11:05:02 +01:00
def __init__(self, name, source, description=None, target=None, platform=None,
conn=None, assistant=None, target_params=None, platform_params=None,
conn_params=None, assistant_params=None):
self.name = name
self.source = source
self.description = description
self.target = target
self.platform = platform
self.connection = conn
self.assistant = assistant
self._set('target_params', target_params)
self._set('platform_params', platform_params)
self._set('conn_params', conn_params)
self._set('assistant_params', assistant_params)
def get_default_config(self):
param_attrs = ['target_params', 'platform_params',
'conn_params', 'assistant_params']
config = {}
for pattr in param_attrs:
for p in getattr(self, pattr):
config[p.name] = p.default
return config
def _set(self, attr, vals):
if vals is None:
vals = []
elif isiterable(vals):
if hasattr(vals, 'values'):
vals = list(vals.values())
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): # pylint: disable=no-self-use
return []
COMMON_TARGET_PARAMS = [
Parameter('working_directory', kind=str,
description='''
2017-10-04 11:05:02 +01:00
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,
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.
2017-10-04 11:05:10 +01:00
Please see ``devlib`` documentation for information on the available
modules.
'''),
Parameter('load_default_modules', kind=bool, default=True,
description='''
A number of modules (e.g. for working with the cpufreq subsystem) are
loaded by default when a Target is instantiated. Setting this to
``True`` would suppress that, ensuring that only the base Target
interface is initialized.
You may want to set this to ``False`` if there is a problem with one
or more default modules on your platform (e.g. your device is
unrooted and cpufreq is not accessible to unprivileged users), or
if ``Target`` initialization is taking too long for your platform.
'''),
Parameter('shell_prompt', kind=regex, default=DEFAULT_SHELL_PROMPT,
description='''
A regex that matches the shell prompt on the target.
'''),
]
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,
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('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.
'''),
Parameter('name', kind=str, default='gem5',
description='''
The name for the gem5 "device".
'''),
]
CONNECTION_PARAMS = {
AdbConnection: [
Parameter(
'device', kind=str,
aliases=['adb_name'],
description="""
ADB device name
"""),
Parameter(
'adb_server', kind=str,
description="""
ADB server to connect to.
"""),
],
SshConnection: [
Parameter(
'host', kind=str, mandatory=True,
description="""
Host name or IP address of the target.
"""),
Parameter(
'username', kind=str, mandatory=True,
description="""
User name to connect with
"""),
Parameter(
'password', kind=str,
description="""
Password to use.
"""),
Parameter(
'keyfile', kind=str,
description="""
Key file to use
"""),
Parameter(
'port', kind=int,
description="""
The port SSH server is listening on on the target.
"""),
Parameter(
'telnet', kind=bool, default=False,
description="""
If set to ``True``, a Telnet connection, rather than
SSH will be used.
"""),
Parameter(
'password_prompt', kind=str,
description="""
Password prompt to expect
"""),
Parameter(
'original_prompt', kind=str,
description="""
Original shell prompt to expect.
"""),
Parameter(
'sudo_cmd', kind=str,
default="sudo -- sh -c {}",
description="""
Sudo command to use. Must have ``{}`` specified
somewhere in the string it indicate where the command
to be run via sudo is to go.
"""),
],
Gem5Connection: [
Parameter(
'host', kind=str, mandatory=False,
description="""
Host name or IP address of the target.
"""),
Parameter(
'username', kind=str, default='root',
description="""
User name to connect to gem5 simulation.
"""),
Parameter(
'password', kind=str,
description="""
Password to use.
"""),
Parameter(
'port', kind=int,
description="""
The port SSH server is listening on on the target.
"""),
Parameter(
'password_prompt', kind=str,
description="""
Password prompt to expect
"""),
Parameter(
'original_prompt', kind=str,
description="""
Original shell prompt to expect.
"""),
],
LocalConnection: [
Parameter(
'password', kind=str,
description="""
Password to use for sudo. if not specified, the user will
be prompted during intialization.
"""),
Parameter(
'keep_password', kind=bool, default=True,
description="""
If ``True`` (the default), the password will be cached in
memory after it is first obtained from the user, so that the
user would not be prompted for it again.
"""),
Parameter(
'unrooted', kind=bool, default=False,
description="""
Indicate that the target should be considered unrooted; do not
attempt sudo or ask the user for their password.
"""),
],
}
CONNECTION_PARAMS['ChromeOsConnection'] = \
CONNECTION_PARAMS[AdbConnection] + CONNECTION_PARAMS[SshConnection]
# name --> ((target_class, conn_class), params_list, defaults)
TARGETS = {
'linux': ((LinuxTarget, SshConnection), COMMON_TARGET_PARAMS, None),
'android': ((AndroidTarget, AdbConnection), COMMON_TARGET_PARAMS +
[Parameter('package_data_directory', kind=str, default='/data/data',
description='''
Directory containing Android data
'''),
], None),
'chromeos': ((ChromeOsTarget, 'ChromeOsConnection'), COMMON_TARGET_PARAMS +
[Parameter('package_data_directory', kind=str, default='/data/data',
description='''
Directory containing Android data
'''),
Parameter('android_working_directory', kind=str,
description='''
On-target working directory that will be used by WA for the
android container. This directory must be writable by the user
WA logs in as without the need for privilege elevation.
'''),
Parameter('android_executables_directory', kind=str,
description='''
On-target directory where WA will install its executable
binaries for the android container. 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).
directory must be writable by the user WA logs in as without
the need for privilege elevation.
'''),
], None),
'local': ((LocalLinuxTarget, LocalConnection), COMMON_TARGET_PARAMS, None),
}
# name --> assistant
ASSISTANTS = {
'linux': LinuxAssistant,
'android': AndroidAssistant,
'local': LinuxAssistant,
'chromeos': ChromeOsAssistant
}
# name --> ((platform_class, conn_class), params_list, defaults, target_defaults)
# Note: normally, connection is defined by the Target name, but
# platforms may choose to override it
# Note: the target_defaults allows you to override common target_params for a
# particular platform. Parameters you can override are in COMMON_TARGET_PARAMS
# Example of overriding one of the target parameters: Replace last None with:
# {'shell_prompt': CUSTOM__SHELL_PROMPT}
PLATFORMS = {
'generic': ((Platform, None), COMMON_PLATFORM_PARAMS, None, None),
'juno': ((Juno, None), COMMON_PLATFORM_PARAMS + VEXPRESS_PLATFORM_PARAMS,
{
'vemsd_mount': '/media/JUNO',
'baudrate': 115200,
'bootloader': 'u-boot',
'hard_reset_method': 'dtr',
},
None),
'tc2': ((TC2, None), COMMON_PLATFORM_PARAMS + VEXPRESS_PLATFORM_PARAMS,
{
'vemsd_mount': '/media/VEMSD',
'baudrate': 38400,
'bootloader': 'bootmon',
'hard_reset_method': 'reboottxt',
}, None),
'gem5': ((Gem5SimulationPlatform, Gem5Connection), GEM5_PLATFORM_PARAMS, None, 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):
# pylint: disable=attribute-defined-outside-init,too-many-locals
result = []
for target_name, target_tuple in TARGETS.items():
(target, conn), target_params = self._get_item(target_tuple)
assistant = ASSISTANTS[target_name]
conn_params = CONNECTION_PARAMS[conn]
for platform_name, platform_tuple in PLATFORMS.items():
platform_target_defaults = platform_tuple[-1]
platform_tuple = platform_tuple[0:-1]
(platform, plat_conn), platform_params = self._get_item(platform_tuple)
# Add target defaults specified in the Platform tuple
target_params = self._apply_param_defaults(target_params,
platform_target_defaults)
name = '{}_{}'.format(platform_name, target_name)
td = TargetDescription(name, self)
td.target = target
td.platform = platform
td.assistant = assistant
td.target_params = target_params
td.platform_params = platform_params
td.assistant_params = assistant.parameters
if plat_conn:
td.conn = plat_conn
td.conn_params = CONNECTION_PARAMS[plat_conn]
else:
td.conn = conn
td.conn_params = conn_params
result.append(td)
return result
def _apply_param_defaults(self, params, defaults): # pylint: disable=no-self-use
'''Adds parameters in the defaults dict to params list.
Return updated params as a list (idempotent function).'''
if not defaults:
return params
param_map = OrderedDict((p.name, copy(p)) for p in params)
for name, value in defaults.items():
if name not in param_map:
raise ValueError('Unexpected default "{}"'.format(name))
param_map[name].default = value
# Convert the OrderedDict to a list to return the same type
return list(param_map.values())
def _get_item(self, item_tuple):
cls, params, defaults = item_tuple
updated_params = self._apply_param_defaults(params, defaults)
return cls, updated_params
_adhoc_target_descriptions = []
def create_target_description(name, *args, **kwargs):
name = identifier(name)
for td in _adhoc_target_descriptions:
if caseless_string(name) == td.name:
msg = 'Target with name "{}" already exists (from source: {})'
raise ValueError(msg.format(name, td.source))
stack = inspect.stack()
# inspect.stack() returns a list of call frame records for the current thread
# in reverse call order. So the first entry is for the current frame and next one
# for the immediate caller. Each entry is a tuple in the format
# (frame_object, module_path, line_no, function_name, source_lines, source_lines_index)
#
# Here we assign the path of the calling module as the "source" for this description.
# because this might be invoked via the add_scription_for_target wrapper, we need to
# check for that, and make sure that we get the info for *its* caller in that case.
if stack[1][3] == 'add_description_for_target':
source = stack[2][1]
else:
source = stack[1][1]
_adhoc_target_descriptions.append(TargetDescription(name, source, *args, **kwargs))
def _get_target_defaults(target):
specificity = 0
res = ('linux', TARGETS['linux']) # fallback to a generic linux target
for name, ttup in TARGETS.items():
if issubclass(target, ttup[0][0]):
new_spec = len(inspect.getmro(ttup[0][0]))
if new_spec > specificity:
res = (name, ttup)
specificity = new_spec
return res
def add_description_for_target(target, description=None, **kwargs):
(base_name, ((_, base_conn), base_params, _)) = _get_target_defaults(target)
if 'target_params' not in kwargs:
kwargs['target_params'] = base_params
if 'platform' not in kwargs:
kwargs['platform'] = Platform
if 'platform_params' not in kwargs:
for (plat, conn), params, _, _ in PLATFORMS.values():
if plat == kwargs['platform']:
kwargs['platform_params'] = params
if conn is not None and kwargs['conn'] is None:
kwargs['conn'] = conn
break
if 'conn' not in kwargs:
kwargs['conn'] = base_conn
if 'conn_params' not in kwargs:
kwargs['conn_params'] = CONNECTION_PARAMS.get(kwargs['conn'])
if 'assistant' not in kwargs:
kwargs['assistant'] = ASSISTANTS.get(base_name)
create_target_description(target.name, target=target, description=description, **kwargs)
class SimpleTargetDescriptor(TargetDescriptor):
name = 'adhoc_targets'
description = """
Returns target descriptions added with ``create_target_description``.
"""
def get_descriptions(self):
return _adhoc_target_descriptions