scripts: add west build, flash, and debug commands

West now supports a mechanism for extension commands. Use it to move
the command implementations that are tightly coupled with boards and
the zephyr build system back into the Zephyr repository.

This patch doesn't include test cases. Those will be moved over in a
subsequent patch.

Signed-off-by: Marti Bolivar <marti@foundries.io>
This commit is contained in:
Marti Bolivar 2019-01-23 08:31:06 -07:00 committed by Carles Cufí
parent 6aa87b6f5b
commit ab82264ace
26 changed files with 2987 additions and 34 deletions

View file

@ -28,6 +28,10 @@ ZEPHYR_BUILD = os.path.abspath(os.environ["ZEPHYR_BUILD"])
# extensions within.
sys.path.insert(0, os.path.join(ZEPHYR_BASE, 'doc', 'extensions'))
# Add the directory which contains the runners package as well,
# for autodoc directives on runners.xyz.
sys.path.insert(0, os.path.join(ZEPHYR_BASE, 'scripts', 'west_commands'))
west_found = False
try:

View file

@ -11,6 +11,14 @@ These use information stored in the CMake cache [#cmakecache]_ to
flash or attach a debugger to a board supported by Zephyr. The CMake
build system commands with the same names directly delegate to West.
.. Add a per-page contents at the top of the page. This page is nested
deeply enough that it doesn't have any subheadings in the main nav.
.. only:: html
.. contents::
:local:
.. _west-flashing:
Flashing: ``west flash``
@ -191,37 +199,57 @@ For example, to print usage information about the ``jlink`` runner::
.. _west-runner:
Library Backend: ``west.runners``
*********************************
Implementation Details
**********************
In keeping with West's :ref:`west-design-constraints`, the flash and
debug commands are wrappers around a separate library that is part of
West, and can be used by other code.
The flash and debug commands are implemented as west *extension
commands*: that is, they are west commands whose source code lives
outside the west repository. Some reasons this choice was made are:
This library is the ``west.runners`` subpackage of West itself. The
central abstraction within this library is ``ZephyrBinaryRunner``, an
abstract class which represents *runner* objects, which can flash
- Their implementations are tightly coupled to the Zephyr build
system, e.g. due to their reliance on CMake cache variables.
- Pull requests adding features to them are almost always motivated by
a corresponding change to an upstream board, so it makes sense to
put them in Zephyr to avoid needing pull requests in multiple
repositories.
- Many users find it natural to search for their implementations in
the Zephyr source tree.
The extension commands are a thin wrapper around a package called
``runners`` (this package is also in the Zephyr tree, in
:file:`scripts/west_commands/runners`).
The central abstraction within this library is ``ZephyrBinaryRunner``,
an abstract class which represents *runner* objects, which can flash
and/or debug Zephyr programs. The set of available runners is
determined by the imported subclasses of ``ZephyrBinaryRunner``.
``ZephyrBinaryRunner`` is available in the ``west.runners.core``
module; individual runner implementations are in other submodules,
such as ``west.runners.nrfjprog``, ``west.runners.openocd``, etc.
``ZephyrBinaryRunner`` is available in the ``runners.core`` module;
individual runner implementations are in other submodules, such as
``runners.nrfjprog``, ``runners.openocd``, etc.
Hacking and APIs
****************
Developers can add support for new ways to flash and debug Zephyr
programs by implementing additional runners. To get this support into
upstream Zephyr, the runner should be added into a new or existing
``west.runners`` module, and imported from
:file:`west/runner/__init__.py`.
``runners`` module, and imported from :file:`runner/__init__.py`.
.. important::
.. note::
Submit any changes to West to its own separate Git repository
(https://github.com/zephyrproject-rtos/west), not to the copy of
West currently present in the Zephyr tree. This copy is a temporary
measure; when West learns how to manage multiple repositories, the
copy will be removed.
The test cases in :file:`scripts/west_commands/tests` add unit test
coverage for the runners package and individual runner classes.
API documentation for the core module can be found in :ref:`west-apis`.
Please try to add tests when adding new runners. Note that if your
changes break existing test cases, CI testing on upstream pull
requests will fail.
API Documentation for the ``runners.core`` module follows.
.. automodule:: runners.core
:members:
Doing it By Hand
****************
@ -236,8 +264,8 @@ e.g. as a source of symbol tables.
By default, these West commands rebuild binaries before flashing and
debugging. This can of course also be accomplished using the usual
targets provided by Zephyr's build system (in fact, that's how West
does it).
targets provided by Zephyr's build system (in fact, that's how these
commands do it).
.. rubric:: Footnotes
@ -249,3 +277,6 @@ does it).
.. _cmake(1):
https://cmake.org/cmake/help/latest/manual/cmake.1.html
.. _namespace package:
https://www.python.org/dev/peps/pep-0420/

View file

@ -5,6 +5,4 @@
West APIs
#########
.. automodule:: west.runners.core
:members:

View file

@ -1,10 +0,0 @@
:orphan:
.. _west-apis:
West APIs
#########
The west APIs are not documented since west was missing during the
documentation build.

17
scripts/west-commands.yml Normal file
View file

@ -0,0 +1,17 @@
west-commands:
- file: scripts/west_commands/build.py
commands:
- name: build
class: Build
- file: scripts/west_commands/flash.py
commands:
- name: flash
class: Flash
- file: scripts/west_commands/debug.py
commands:
- name: debug
class: Debug
- name: debugserver
class: DebugServer
- name: attach
class: Attach

View file

@ -0,0 +1,11 @@
This directory contains implementations for west commands which are
tightly coupled to the zephyr tree. Currently, those are the build,
flash, and debug commands.
Before adding more here, consider whether you might want to put new
extensions in upstream west. For example, any commands which operate
on the multi-repo need to be in upstream west, not here. Try to limit
what goes in here to just those files that change along with Zephyr
itself.
Thanks!

View file

@ -0,0 +1,290 @@
# Copyright (c) 2018 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import os
from west import log
from west import cmake
from west.build import DEFAULT_BUILD_DIR, DEFAULT_CMAKE_GENERATOR, \
is_zephyr_build
from west.commands import WestCommand
BUILD_DESCRIPTION = '''\
Convenience wrapper for building Zephyr applications.
This command attempts to do what you mean when run from a Zephyr
application source or a pre-existing build directory:
- When "west build" is run from a Zephyr build directory, the source
directory is obtained from the CMake cache, and that build directory
is re-compiled.
- Otherwise, the source directory defaults to the current working
directory, so running "west build" from a Zephyr application's
source directory compiles it.
The source and build directories can be explicitly set with the
--source-dir and --build-dir options. The build directory defaults to
'build' if it is not auto-detected. The build directory is always
created if it does not exist.
This command runs CMake to generate a build system if one is not
present in the build directory, then builds the application.
Subsequent builds try to avoid re-running CMake; you can force it
to run by setting --cmake.
To pass additional options to CMake, give them as extra arguments
after a '--' For example, "west build -- -DOVERLAY_CONFIG=some.conf" sets
an overlay config file. (Doing this forces a CMake run.)'''
class Build(WestCommand):
def __init__(self):
super(Build, self).__init__(
'build',
'compile a Zephyr application',
BUILD_DESCRIPTION,
accepts_unknown_args=False)
self.source_dir = None
'''Source directory for the build, or None on error.'''
self.build_dir = None
'''Final build directory used to run the build, or None on error.'''
self.created_build_dir = False
'''True if the build directory was created; False otherwise.'''
self.run_cmake = False
'''True if CMake was run; False otherwise.
Note: this only describes CMake runs done by this command. The
build system generated by CMake may also update itself due to
internal logic.'''
self.cmake_cache = None
'''Final parsed CMake cache for the build, or None on error.'''
def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(
self.name,
help=self.help,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=self.description)
# Remember to update scripts/west-completion.bash if you add or remove
# flags
parser.add_argument('-b', '--board',
help='''Board to build for (must be given for the
first build, can be omitted later)''')
parser.add_argument('-s', '--source-dir',
help='''Explicitly set the source directory.
If not given and rebuilding an existing Zephyr
build directory, this is taken from the CMake
cache. Otherwise, the current directory is
assumed.''')
parser.add_argument('-d', '--build-dir',
help='''Explicitly sets the build directory.
If not given and the current directory is a Zephyr
build directory, it will be used; otherwise, "{}"
is assumed. The directory will be created if
it doesn't exist.'''.format(DEFAULT_BUILD_DIR))
parser.add_argument('-t', '--target',
help='''Override the build system target (e.g.
'clean', 'pristine', etc.)''')
parser.add_argument('-c', '--cmake', action='store_true',
help='Force CMake to run')
parser.add_argument('-f', '--force', action='store_true',
help='Ignore any errors and try to build anyway')
parser.add_argument('cmake_opts', nargs='*', metavar='cmake_opt',
help='Extra option to pass to CMake; implies -c')
return parser
def do_run(self, args, ignored):
self.args = args # Avoid having to pass them around
log.dbg('args:', args, level=log.VERBOSE_EXTREME)
self._sanity_precheck()
self._setup_build_dir()
if is_zephyr_build(self.build_dir):
self._update_cache()
if self.args.cmake or self.args.cmake_opts:
self.run_cmake = True
else:
self.run_cmake = True
self._setup_source_dir()
self._sanity_check()
log.inf('source directory: {}'.format(self.source_dir), colorize=True)
log.inf('build directory: {}{}'.
format(self.build_dir,
(' (created)' if self.created_build_dir
else '')),
colorize=True)
if self.cmake_cache:
board = self.cmake_cache.get('CACHED_BOARD')
elif self.args.board:
board = self.args.board
else:
board = 'UNKNOWN' # shouldn't happen
log.inf('BOARD:', board, colorize=True)
self._run_cmake(self.args.cmake_opts)
self._sanity_check()
self._update_cache()
extra_args = ['--target', args.target] if args.target else []
cmake.run_build(self.build_dir, extra_args=extra_args)
def _sanity_precheck(self):
app = self.args.source_dir
if app:
if not os.path.isdir(app):
self._check_force('source directory {} does not exist'.
format(app))
elif 'CMakeLists.txt' not in os.listdir(app):
self._check_force("{} doesn't contain a CMakeLists.txt".
format(app))
def _update_cache(self):
try:
self.cmake_cache = cmake.CMakeCache.from_build_dir(self.build_dir)
except FileNotFoundError:
pass
def _setup_build_dir(self):
# Initialize build_dir and created_build_dir attributes.
log.dbg('setting up build directory', level=log.VERBOSE_EXTREME)
if self.args.build_dir:
build_dir = self.args.build_dir
else:
cwd = os.getcwd()
if is_zephyr_build(cwd):
build_dir = cwd
else:
build_dir = DEFAULT_BUILD_DIR
build_dir = os.path.abspath(build_dir)
if os.path.exists(build_dir):
if not os.path.isdir(build_dir):
log.die('build directory {} exists and is not a directory'.
format(build_dir))
else:
os.makedirs(build_dir, exist_ok=False)
self.created_build_dir = True
self.run_cmake = True
self.build_dir = build_dir
def _setup_source_dir(self):
# Initialize source_dir attribute, either from command line argument,
# implicitly from the build directory's CMake cache, or using the
# default (current working directory).
log.dbg('setting up source directory', level=log.VERBOSE_EXTREME)
if self.args.source_dir:
source_dir = self.args.source_dir
elif self.cmake_cache:
source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
if not source_dir:
# Maybe Zephyr changed the key? Give the user a way
# to retry, at least.
log.die("can't determine application from build directory "
"{}, please specify an application to build".
format(self.build_dir))
else:
source_dir = os.getcwd()
self.source_dir = os.path.abspath(source_dir)
def _sanity_check(self):
# Sanity check the build configuration.
# Side effect: may update cmake_cache attribute.
log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME)
if self.source_dir == self.build_dir:
# There's no forcing this.
log.die('source and build directory {} cannot be the same; '
'use --build-dir {} to specify a build directory'.
format(self.source_dir, self.build_dir))
srcrel = os.path.relpath(self.source_dir)
if is_zephyr_build(self.source_dir):
self._check_force('it looks like {srcrel} is a build directory: '
'did you mean -build-dir {srcrel} instead?'.
format(srcrel=srcrel))
elif 'CMakeLists.txt' not in os.listdir(self.source_dir):
self._check_force('source directory "{srcrel}" does not contain '
'a CMakeLists.txt; is that really what you '
'want to build? (Use -s SOURCE_DIR to specify '
'the application source directory)'.
format(srcrel=srcrel))
if not is_zephyr_build(self.build_dir) and not self.args.board:
self._check_force('this looks like a new or clean build, '
'please provide --board')
if not self.cmake_cache:
return # That's all we can check without a cache.
cached_app = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
log.dbg('APPLICATION_SOURCE_DIR:', cached_app,
level=log.VERBOSE_EXTREME)
source_abs = (os.path.abspath(self.args.source_dir)
if self.args.source_dir else None)
cached_abs = os.path.abspath(cached_app) if cached_app else None
if cached_abs and source_abs and source_abs != cached_abs:
self._check_force('build directory "{}" is for application "{}", '
'but source directory "{}" was specified; '
'please clean it or use --build-dir to set '
'another build directory'.
format(self.build_dir, cached_abs,
source_abs))
self.run_cmake = True # If they insist, we need to re-run cmake.
cached_board = self.cmake_cache.get('CACHED_BOARD')
log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME)
if not cached_board and not self.args.board:
if self.created_build_dir:
self._check_force(
'Building for the first time: you must provide --board')
else:
self._check_force(
'Board is missing or unknown, please provide --board')
if self.args.board and cached_board and \
self.args.board != cached_board:
self._check_force('Build directory {} targets board {}, '
'but board {} was specified. (Clean that '
'directory or use --build-dir to specify '
'a different one.)'.
format(self.build_dir, cached_board,
self.args.board))
def _check_force(self, msg):
if not self.args.force:
log.err(msg)
log.die('refusing to proceed without --force due to above error')
def _run_cmake(self, cmake_opts):
if not self.run_cmake:
log.dbg('not running cmake; build system is present')
return
# It's unfortunate to have to use the undocumented -B and -H
# options to set the source and binary directories.
#
# However, it's the only known way to set that directory and
# run CMake from the current working directory. This is
# important because users expect invocations like this to Just
# Work:
#
# west build -- -DOVERLAY_CONFIG=relative-path.conf
final_cmake_args = ['-B{}'.format(self.build_dir),
'-H{}'.format(self.source_dir),
'-G{}'.format(DEFAULT_CMAKE_GENERATOR)]
if self.args.board:
final_cmake_args.append('-DBOARD={}'.format(self.args.board))
if cmake_opts:
final_cmake_args.extend(cmake_opts)
cmake.run_cmake(final_cmake_args)

View file

@ -0,0 +1,75 @@
# Copyright (c) 2018 Open Source Foundries Limited.
# Copyright 2019 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
'''west "debug" and "debugserver" commands.'''
from textwrap import dedent
from west.commands import WestCommand
from run_common import desc_common, add_parser_common, do_run_common
class Debug(WestCommand):
def __init__(self):
super(Debug, self).__init__(
'debug',
'flash and interactively debug a Zephyr application',
dedent('''
Connect to the board, program the flash, and start a
debugging session.\n\n''') +
desc_common('debug'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')
class DebugServer(WestCommand):
def __init__(self):
super(DebugServer, self).__init__(
'debugserver',
'connect to board and launch a debug server',
dedent('''
Connect to the board and launch a debug server which accepts
incoming connections for debugging the connected board.
The debug server binds to a known port, and allows client software
started elsewhere to connect to it and debug the running
Zephyr image.\n\n''') +
desc_common('debugserver'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')
class Attach(WestCommand):
def __init__(self):
super(Attach, self).__init__(
'attach',
'interactively debug a board',
dedent('''
Like 'debug', this connects to the board and starts a debugging
session, but it doesn't reflash the program on the board.\n\n''') +
desc_common('attach'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_DEBUG_RUNNER')

View file

@ -0,0 +1,31 @@
# Copyright (c) 2018 Open Source Foundries Limited.
# Copyright 2019 Foundries.io
#
# SPDX-License-Identifier: Apache-2.0
'''west "flash" command'''
from textwrap import dedent
from west.commands import WestCommand
from run_common import desc_common, add_parser_common, do_run_common
class Flash(WestCommand):
def __init__(self):
super(Flash, self).__init__(
'flash',
'flash and run a binary on a board',
dedent('''
Connects to the board and reprograms it with a new binary\n\n''') +
desc_common('flash'),
accepts_unknown_args=True)
def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self)
def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args,
'ZEPHYR_BOARD_FLASH_RUNNER')

View file

@ -0,0 +1,454 @@
# Copyright (c) 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Common code used by commands which execute runners.
'''
import argparse
from os import getcwd, path
from subprocess import CalledProcessError
import textwrap
from west import cmake
from west import log
from west import util
from west.build import DEFAULT_BUILD_DIR, is_zephyr_build
from west.commands import CommandContextError
from runners import get_runner_cls, ZephyrBinaryRunner
from runners.core import RunnerConfig
# Context-sensitive help indentation.
# Don't change this, or output from argparse won't match up.
INDENT = ' ' * 2
def add_parser_common(parser_adder, command):
parser = parser_adder.add_parser(
command.name,
formatter_class=argparse.RawDescriptionHelpFormatter,
help=command.help,
description=command.description)
# Remember to update scripts/west-completion.bash if you add or remove
# flags
parser.add_argument('-H', '--context', action='store_true',
help='''Rebuild application and print context-sensitive
help; this may be combined with --runner to restrict
output to a given runner.''')
group = parser.add_argument_group(title='General Options')
group.add_argument('-d', '--build-dir',
help='''Build directory to obtain runner information
from. If not given, this command tries to use build/
and then the current working directory, in that
order.''')
group.add_argument('-c', '--cmake-cache',
help='''Path to CMake cache file containing runner
configuration (this is generated by the Zephyr
build system when compiling binaries);
default: {}.
If this is a relative path, it is assumed relative to
the build directory. An absolute path can also be
given instead.'''.format(cmake.DEFAULT_CACHE))
group.add_argument('-r', '--runner',
help='''If given, overrides any cached {}
runner.'''.format(command.name))
group.add_argument('--skip-rebuild', action='store_true',
help='''If given, do not rebuild the application
before running {} commands.'''.format(command.name))
group = parser.add_argument_group(
title='Configuration overrides',
description=textwrap.dedent('''\
These values usually come from the Zephyr build system itself
as stored in the CMake cache; providing these options
overrides those settings.'''))
# Important:
#
# 1. The destination variables of these options must match
# the RunnerConfig slots.
# 2. The default values for all of these must be None.
#
# This is how we detect if the user provided them or not when
# overriding values from the cached configuration.
command_verb = "flash" if command == "flash" else "debug"
group.add_argument('--board-dir',
help='Zephyr board directory')
group.add_argument('--elf-file',
help='Path to elf file to {0}'.format(command_verb))
group.add_argument('--hex-file',
help='Path to hex file to {0}'.format(command_verb))
group.add_argument('--bin-file',
help='Path to binary file to {0}'.format(command_verb))
group.add_argument('--gdb',
help='Path to GDB, if applicable')
group.add_argument('--openocd',
help='Path to OpenOCD, if applicable')
group.add_argument(
'--openocd-search',
help='Path to add to OpenOCD search path, if applicable')
return parser
def desc_common(command_name):
return textwrap.dedent('''\
Any options not recognized by this command are passed to the
back-end {command} runner (run "west {command} --context"
for help on available runner-specific options).
If you need to pass an option to a runner which has the
same name as one recognized by this command, you can
end argument parsing with a '--', like so:
west {command} --{command}-arg=value -- --runner-arg=value2
'''.format(**{'command': command_name}))
def cached_runner_config(build_dir, cache):
'''Parse the RunnerConfig from a build directory and CMake Cache.'''
board_dir = cache['ZEPHYR_RUNNER_CONFIG_BOARD_DIR']
elf_file = cache.get('ZEPHYR_RUNNER_CONFIG_ELF_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_ELF'])
hex_file = cache.get('ZEPHYR_RUNNER_CONFIG_HEX_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_HEX'])
bin_file = cache.get('ZEPHYR_RUNNER_CONFIG_BIN_FILE',
cache['ZEPHYR_RUNNER_CONFIG_KERNEL_BIN'])
gdb = cache.get('ZEPHYR_RUNNER_CONFIG_GDB')
openocd = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD')
openocd_search = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD_SEARCH')
return RunnerConfig(build_dir, board_dir,
elf_file, hex_file, bin_file,
gdb=gdb, openocd=openocd,
openocd_search=openocd_search)
def _override_config_from_namespace(cfg, namespace):
'''Override a RunnerConfig's contents with command-line values.'''
for var in cfg.__slots__:
if var in namespace:
val = getattr(namespace, var)
if val is not None:
setattr(cfg, var, val)
def _build_dir(args, die_if_none=True):
# Get the build directory for the given argument list and environment.
if args.build_dir:
return args.build_dir
cwd = getcwd()
default = path.join(cwd, DEFAULT_BUILD_DIR)
if is_zephyr_build(default):
return default
elif is_zephyr_build(cwd):
return cwd
elif die_if_none:
log.die('--build-dir was not given, and neither {} '
'nor {} are zephyr build directories.'.
format(default, cwd))
else:
return None
def do_run_common(command, args, runner_args, cached_runner_var):
if args.context:
_dump_context(command, args, runner_args, cached_runner_var)
return
command_name = command.name
build_dir = _build_dir(args)
if not args.skip_rebuild:
try:
cmake.run_build(build_dir)
except CalledProcessError:
if args.build_dir:
log.die('cannot run {}, build in {} failed'.format(
command_name, args.build_dir))
else:
log.die('cannot run {}; no --build-dir given and build in '
'current directory {} failed'.format(command_name,
build_dir))
# Runner creation, phase 1.
#
# Get the default runner name from the cache, allowing a command
# line override. Get the ZephyrBinaryRunner class by name, and
# make sure it supports the command.
cache_file = path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE)
cache = cmake.CMakeCache(cache_file)
board = cache['CACHED_BOARD']
available = cache.get_list('ZEPHYR_RUNNERS')
if not available:
log.wrn('No cached runners are available in', cache_file)
runner = args.runner or cache.get(cached_runner_var)
if runner is None:
raise CommandContextError(textwrap.dedent("""
No {} runner available for {}. Please either specify one
manually, or check your board's documentation for
alternative instructions.""".format(command_name, board)))
log.inf('Using runner:', runner)
if runner not in available:
log.wrn('Runner {} is not configured for use with {}, '
'this may not work'.format(runner, board))
runner_cls = get_runner_cls(runner)
if command_name not in runner_cls.capabilities().commands:
log.die('Runner {} does not support command {}'.format(
runner, command_name))
# Runner creation, phase 2.
#
# At this point, the common options above are already parsed in
# 'args', and unrecognized arguments are in 'runner_args'.
#
# - Pull the RunnerConfig out of the cache
# - Override cached values with applicable command-line options
cfg = cached_runner_config(build_dir, cache)
_override_config_from_namespace(cfg, args)
# Runner creation, phase 3.
#
# - Pull out cached runner arguments, and append command-line
# values (which should override the cache)
# - Construct a runner-specific argument parser to handle cached
# values plus overrides given in runner_args
# - Parse arguments and create runner instance from final
# RunnerConfig and parsed arguments.
cached_runner_args = cache.get_list(
'ZEPHYR_RUNNER_ARGS_{}'.format(cmake.make_c_identifier(runner)))
assert isinstance(runner_args, list), runner_args
# If the user passed -- to force the parent argument parser to stop
# parsing, it will show up here, and needs to be filtered out.
runner_args = [arg for arg in runner_args if arg != '--']
final_runner_args = cached_runner_args + runner_args
parser = argparse.ArgumentParser(prog=runner)
runner_cls.add_parser(parser)
parsed_args, unknown = parser.parse_known_args(args=final_runner_args)
if unknown:
raise CommandContextError('Runner', runner,
'received unknown arguments', unknown)
runner = runner_cls.create(cfg, parsed_args)
runner.run(command_name)
#
# Context-specific help
#
def _dump_context(command, args, runner_args, cached_runner_var):
build_dir = _build_dir(args, die_if_none=False)
# Try to figure out the CMake cache file based on the build
# directory or an explicit argument.
if build_dir is not None:
cache_file = path.abspath(
path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE))
elif args.cmake_cache:
cache_file = path.abspath(args.cmake_cache)
else:
cache_file = None
# Load the cache itself, if possible.
if cache_file is None:
log.wrn('No build directory (--build-dir) or CMake cache '
'(--cache-file) given or found; output will be limited')
cache = None
else:
try:
cache = cmake.CMakeCache(cache_file)
except Exception:
log.die('Cannot load cache {}.'.format(cache_file))
# If we have a build directory, try to ensure build artifacts are
# up to date. If that doesn't work, still try to print information
# on a best-effort basis.
if build_dir and not args.skip_rebuild:
try:
cmake.run_build(build_dir)
except CalledProcessError:
msg = 'Failed re-building application; cannot load context. '
if args.build_dir:
msg += 'Is {} the right --build-dir?'.format(args.build_dir)
else:
msg += textwrap.dedent('''\
Use --build-dir (-d) to specify a build directory; the one
used was {}.'''.format(build_dir))
log.die('\n'.join(textwrap.wrap(msg, initial_indent='',
subsequent_indent=INDENT,
break_on_hyphens=False)))
if cache is None:
_dump_no_context_info(command, args)
if not args.runner:
return
if args.runner:
# Just information on one runner was requested.
_dump_one_runner_info(cache, args, build_dir, INDENT)
return
board = cache['CACHED_BOARD']
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
command.name in cls.capabilities().commands}
available = [r for r in cache.get_list('ZEPHYR_RUNNERS') if r in all_cls]
available_cls = {r: all_cls[r] for r in available if r in all_cls}
default_runner = cache.get(cached_runner_var)
cfg = cached_runner_config(build_dir, cache)
log.inf('All Zephyr runners which support {}:'.format(command.name),
colorize=True)
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
log.inf(line)
log.inf('(Not all may work with this build, see available runners below.)',
colorize=True)
if cache is None:
log.warn('Missing or invalid CMake cache {}; there is no context.',
'Use --build-dir to specify the build directory.')
return
log.inf('Build directory:', colorize=True)
log.inf(INDENT + build_dir)
log.inf('Board:', colorize=True)
log.inf(INDENT + board)
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + cache_file)
if not available:
# Bail with a message if no runners are available.
msg = ('No runners available for {}. '
'Consult the documentation for instructions on how to run '
'binaries on this target.').format(board)
for line in util.wrap(msg, ''):
log.inf(line, colorize=True)
return
log.inf('Available {} runners:'.format(command.name), colorize=True)
log.inf(INDENT + ', '.join(available))
log.inf('Additional options for available', command.name, 'runners:',
colorize=True)
for runner in available:
_dump_runner_opt_help(runner, all_cls[runner])
log.inf('Default {} runner:'.format(command.name), colorize=True)
log.inf(INDENT + default_runner)
_dump_runner_config(cfg, '', INDENT)
log.inf('Runner-specific information:', colorize=True)
for runner in available:
log.inf('{}{}:'.format(INDENT, runner), colorize=True)
_dump_runner_cached_opts(cache, runner, INDENT * 2, INDENT * 3)
_dump_runner_caps(available_cls[runner], INDENT * 2)
if len(available) > 1:
log.inf('(Add -r RUNNER to just print information about one runner.)',
colorize=True)
def _dump_no_context_info(command, args):
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
command.name in cls.capabilities().commands}
log.inf('All Zephyr runners which support {}:'.format(command.name),
colorize=True)
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
log.inf(line)
if not args.runner:
log.inf('Add -r RUNNER to print more information about any runner.',
colorize=True)
def _dump_one_runner_info(cache, args, build_dir, indent):
runner = args.runner
cls = get_runner_cls(runner)
if cache is None:
_dump_runner_opt_help(runner, cls)
_dump_runner_caps(cls, '')
return
available = runner in cache.get_list('ZEPHYR_RUNNERS')
cfg = cached_runner_config(build_dir, cache)
log.inf('Build directory:', colorize=True)
log.inf(INDENT + build_dir)
log.inf('Board:', colorize=True)
log.inf(INDENT + cache['CACHED_BOARD'])
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + cache.cache_file)
log.inf(runner, 'is available:', 'yes' if available else 'no',
colorize=True)
_dump_runner_opt_help(runner, cls)
_dump_runner_config(cfg, '', indent)
if available:
_dump_runner_cached_opts(cache, runner, '', indent)
_dump_runner_caps(cls, '')
if not available:
log.wrn('Runner', runner, 'is not configured in this build.')
def _dump_runner_caps(cls, base_indent):
log.inf('{}Capabilities:'.format(base_indent), colorize=True)
log.inf('{}{}'.format(base_indent + INDENT, cls.capabilities()))
def _dump_runner_opt_help(runner, cls):
# Construct and print the usage text
dummy_parser = argparse.ArgumentParser(prog='', add_help=False)
cls.add_parser(dummy_parser)
formatter = dummy_parser._get_formatter()
for group in dummy_parser._action_groups:
# Break the abstraction to filter out the 'flash', 'debug', etc.
# TODO: come up with something cleaner (may require changes
# in the runner core).
actions = group._group_actions
if len(actions) == 1 and actions[0].dest == 'command':
# This is the lone positional argument. Skip it.
continue
formatter.start_section('REMOVE ME')
formatter.add_text(group.description)
formatter.add_arguments(actions)
formatter.end_section()
# Get the runner help, with the "REMOVE ME" string gone
runner_help = '\n'.join(formatter.format_help().splitlines()[1:])
log.inf('{} options:'.format(runner), colorize=True)
log.inf(runner_help)
def _dump_runner_config(cfg, initial_indent, subsequent_indent):
log.inf('{}Cached common runner configuration:'.format(initial_indent),
colorize=True)
for var in cfg.__slots__:
log.inf('{}--{}={}'.format(subsequent_indent, var, getattr(cfg, var)))
def _dump_runner_cached_opts(cache, runner, initial_indent, subsequent_indent):
runner_args = _get_runner_args(cache, runner)
if not runner_args:
return
log.inf('{}Cached runner-specific options:'.format(initial_indent),
colorize=True)
for arg in runner_args:
log.inf('{}{}'.format(subsequent_indent, arg))
def _get_runner_args(cache, runner):
runner_ident = cmake.make_c_identifier(runner)
args_var = 'ZEPHYR_RUNNER_ARGS_{}'.format(runner_ident)
return cache.get_list(args_var)

View file

@ -0,0 +1,35 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
from runners.core import ZephyrBinaryRunner
# We import these here to ensure the ZephyrBinaryRunner subclasses are
# defined; otherwise, ZephyrBinaryRunner.create_for_shell_script()
# won't work.
# Explicitly silence the unused import warning.
# flake8: noqa: F401
from runners import arc
from runners import bossac
from runners import dfu
from runners import esp32
from runners import jlink
from runners import nios2
from runners import nrfjprog
from runners import nsim
from runners import openocd
from runners import pyocd
from runners import qemu
from runners import xtensa
from runners import intel_s1000
from runners import blackmagicprobe
def get_runner_cls(runner):
'''Get a runner's class object, given its name.'''
for cls in ZephyrBinaryRunner.get_runners():
if cls.name() == runner:
return cls
raise ValueError('unknown runner "{}"'.format(runner))
__all__ = ['ZephyrBinaryRunner', 'get_runner_cls']

View file

@ -0,0 +1,107 @@
# Copyright (c) 2017 Linaro Limited.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''ARC architecture-specific runners.'''
from os import path
from runners.core import ZephyrBinaryRunner
DEFAULT_ARC_TCL_PORT = 6333
DEFAULT_ARC_TELNET_PORT = 4444
DEFAULT_ARC_GDB_PORT = 3333
class EmStarterKitBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the EM Starterkit board, using openocd.'''
# This unusual 'flash' implementation matches the original shell script.
#
# It works by starting a GDB server in a separate session, connecting a
# client to it to load the program, and running 'continue' within the
# client to execute the application.
#
def __init__(self, cfg,
tui=False, tcl_port=DEFAULT_ARC_TCL_PORT,
telnet_port=DEFAULT_ARC_TELNET_PORT,
gdb_port=DEFAULT_ARC_GDB_PORT):
super(EmStarterKitBinaryRunner, self).__init__(cfg)
self.gdb_cmd = [cfg.gdb] + (['-tui'] if tui else [])
search_args = []
if cfg.openocd_search is not None:
search_args = ['-s', cfg.openocd_search]
self.openocd_cmd = [cfg.openocd or 'openocd'] + search_args
self.tcl_port = tcl_port
self.telnet_port = telnet_port
self.gdb_port = gdb_port
@classmethod
def name(cls):
return 'em-starterkit'
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--tcl-port', default=DEFAULT_ARC_TCL_PORT,
help='openocd TCL port, defaults to 6333')
parser.add_argument('--telnet-port', default=DEFAULT_ARC_TELNET_PORT,
help='openocd telnet port, defaults to 4444')
parser.add_argument('--gdb-port', default=DEFAULT_ARC_GDB_PORT,
help='openocd gdb port, defaults to 3333')
@classmethod
def create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')
return EmStarterKitBinaryRunner(
cfg,
tui=args.tui, tcl_port=args.tcl_port, telnet_port=args.telnet_port,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
kwargs['openocd-cfg'] = path.join(self.cfg.board_dir, 'support',
'openocd.cfg')
if command in {'flash', 'debug'}:
self.flash_debug(command, **kwargs)
else:
self.debugserver(**kwargs)
def flash_debug(self, command, **kwargs):
config = kwargs['openocd-cfg']
server_cmd = (self.openocd_cmd +
['-f', config] +
['-c', 'tcl_port {}'.format(self.tcl_port),
'-c', 'telnet_port {}'.format(self.telnet_port),
'-c', 'gdb_port {}'.format(self.gdb_port),
'-c', 'init',
'-c', 'targets',
'-c', 'halt'])
continue_arg = []
if command == 'flash':
continue_arg = ['-ex', 'set confirm off', '-ex', 'monitor resume',
'-ex', 'quit']
gdb_cmd = (self.gdb_cmd +
['-ex', 'target remote :{}'.format(self.gdb_port),
'-ex', 'load'] +
continue_arg +
[self.cfg.elf_file])
self.run_server_and_client(server_cmd, gdb_cmd)
def debugserver(self, **kwargs):
config = kwargs['openocd-cfg']
cmd = (self.openocd_cmd +
['-f', config,
'-c', 'init',
'-c', 'targets',
'-c', 'reset halt'])
self.check_call(cmd)

View file

@ -0,0 +1,96 @@
# Copyright (c) 2018 Roman Tataurov <diytronic@yandex.ru>
# Modified 2018 Tavish Naruka <tavishnaruka@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with Black Magic Probe.'''
# https://github.com/blacksphere/blackmagic/wiki
from runners.core import ZephyrBinaryRunner, RunnerCaps
class BlackMagicProbeRunner(ZephyrBinaryRunner):
'''Runner front-end for Black Magic probe.'''
def __init__(self, cfg, gdb_serial):
super(BlackMagicProbeRunner, self).__init__(cfg)
self.gdb = [cfg.gdb] if cfg.gdb else None
self.elf_file = cfg.elf_file
self.gdb_serial = gdb_serial
@classmethod
def name(cls):
return 'blackmagicprobe'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'attach'})
@classmethod
def create(cls, cfg, args):
return BlackMagicProbeRunner(cfg, args.gdb_serial)
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--gdb-serial', default='/dev/ttyACM0',
help='GDB serial port')
def bmp_flash(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot flash; gdb is missing')
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "load {}".format(self.elf_file),
'-ex', "kill",
'-ex', "quit",
'-silent'])
self.check_call(command)
def bmp_attach(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot attach; gdb is missing')
if self.elf_file is None:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1"])
else:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file)])
self.check_call(command)
def bmp_debug(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(self.gdb_serial),
'-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file),
'-ex', "load {}".format(self.elf_file)])
self.check_call(command)
def do_run(self, command, **kwargs):
if command == 'flash':
self.bmp_flash(command, **kwargs)
elif command == 'debug':
self.bmp_debug(command, **kwargs)
elif command == 'attach':
self.bmp_attach(command, **kwargs)
else:
self.bmp_flash(command, **kwargs)

View file

@ -0,0 +1,54 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''bossac-specific runner (flash only) for Atmel SAM microcontrollers.'''
import platform
from runners.core import ZephyrBinaryRunner, RunnerCaps
DEFAULT_BOSSAC_PORT = '/dev/ttyACM0'
class BossacBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for bossac.'''
def __init__(self, cfg, bossac='bossac', port=DEFAULT_BOSSAC_PORT):
super(BossacBinaryRunner, self).__init__(cfg)
self.bossac = bossac
self.port = port
@classmethod
def name(cls):
return 'bossac'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--bossac', default='bossac',
help='path to bossac, default is bossac')
parser.add_argument('--bossac-port', default='/dev/ttyACM0',
help='serial port to use, default is /dev/ttyACM0')
@classmethod
def create(cls, cfg, args):
return BossacBinaryRunner(cfg, bossac=args.bossac,
port=args.bossac_port)
def do_run(self, command, **kwargs):
if platform.system() != 'Linux':
msg = 'CAUTION: No flash tool for your host system found!'
raise NotImplementedError(msg)
cmd_stty = ['stty', '-F', self.port, 'raw', 'ispeed', '1200',
'ospeed', '1200', 'cs8', '-cstopb', 'ignpar', 'eol', '255',
'eof', '255']
cmd_flash = [self.bossac, '-p', self.port, '-R', '-e', '-w', '-v',
'-b', self.cfg.bin_file]
self.check_call(cmd_stty)
self.check_call(cmd_flash)

View file

@ -0,0 +1,508 @@
#! /usr/bin/env python3
# Copyright (c) 2017 Linaro Limited.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
"""Zephyr binary runner core interfaces
This provides the core ZephyrBinaryRunner class meant for public use,
as well as some other helpers for concrete runner classes.
"""
import abc
import argparse
import os
import platform
import signal
import subprocess
from west import log
from west.util import quote_sh_list
# Turn on to enable just printing the commands that would be run,
# without actually running them. This can break runners that are expecting
# output or if one command depends on another, so it's just for debugging.
JUST_PRINT = False
class _DebugDummyPopen:
def terminate(self):
pass
def wait(self):
pass
MAX_PORT = 49151
class NetworkPortHelper:
'''Helper class for dealing with local IP network ports.'''
def get_unused_ports(self, starting_from):
'''Find unused network ports, starting at given values.
starting_from is an iterable of ports the caller would like to use.
The return value is an iterable of ports, in the same order, using
the given values if they were unused, or the next sequentially
available unused port otherwise.
Ports may be bound between this call's check and actual usage, so
callers still need to handle errors involving returned ports.'''
start = list(starting_from)
used = self._used_now()
ret = []
for desired in start:
port = desired
while port in used:
port += 1
if port > MAX_PORT:
msg = "ports above {} are in use"
raise ValueError(msg.format(desired))
used.add(port)
ret.append(port)
return ret
def _used_now(self):
handlers = {
'Windows': self._used_now_windows,
'Linux': self._used_now_linux,
'Darwin': self._used_now_darwin,
}
handler = handlers[platform.system()]
return handler()
def _used_now_windows(self):
cmd = ['netstat', '-a', '-n', '-p', 'tcp']
return self._parser_windows(cmd)
def _used_now_linux(self):
cmd = ['ss', '-a', '-n', '-t']
return self._parser_linux(cmd)
def _used_now_darwin(self):
cmd = ['netstat', '-a', '-n', '-p', 'tcp']
return self._parser_darwin(cmd)
def _parser_windows(self, cmd):
out = subprocess.check_output(cmd).split(b'\r\n')
used_bytes = [x.split()[1].rsplit(b':', 1)[1] for x in out
if x.startswith(b' TCP')]
return {int(b) for b in used_bytes}
def _parser_linux(self, cmd):
out = subprocess.check_output(cmd).splitlines()[1:]
used_bytes = [s.split()[3].rsplit(b':', 1)[1] for s in out]
return {int(b) for b in used_bytes}
def _parser_darwin(self, cmd):
out = subprocess.check_output(cmd).split(b'\n')
used_bytes = [x.split()[3].rsplit(b':', 1)[1] for x in out
if x.startswith(b'tcp')]
return {int(b) for b in used_bytes}
class BuildConfiguration:
'''This helper class provides access to build-time configuration.
Configuration options can be read as if the object were a dict,
either object['CONFIG_FOO'] or object.get('CONFIG_FOO').
Configuration values in .config and generated_dts_board.conf are
available.'''
def __init__(self, build_dir):
self.build_dir = build_dir
self.options = {}
self._init()
def __getitem__(self, item):
return self.options[item]
def get(self, option, *args):
return self.options.get(option, *args)
def _init(self):
build_z = os.path.join(self.build_dir, 'zephyr')
generated = os.path.join(build_z, 'include', 'generated')
files = [os.path.join(build_z, '.config'),
os.path.join(generated, 'generated_dts_board.conf')]
for f in files:
self._parse(f)
def _parse(self, filename):
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
option, value = line.split('=', 1)
self.options[option] = self._parse_value(value)
def _parse_value(self, value):
if value.startswith('"') or value.startswith("'"):
return value.split()
try:
return int(value, 0)
except ValueError:
return value
class RunnerCaps:
'''This class represents a runner class's capabilities.
Each capability is represented as an attribute with the same
name. Flag attributes are True or False.
Available capabilities:
- commands: set of supported commands; default is {'flash',
'debug', 'debugserver', 'attach'}.
- flash_addr: whether the runner supports flashing to an
arbitrary address. Default is False. If true, the runner
must honor the --dt-flash option.
'''
def __init__(self,
commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=False):
self.commands = commands
self.flash_addr = bool(flash_addr)
def __str__(self):
return 'RunnerCaps(commands={}, flash_addr={})'.format(
self.commands, self.flash_addr)
class RunnerConfig:
'''Runner execution-time configuration.
This is a common object shared by all runners. Individual runners
can register specific configuration options using their
do_add_parser() hooks.
This class's __slots__ contains exactly the configuration variables.
'''
__slots__ = ['build_dir', 'board_dir', 'elf_file', 'hex_file',
'bin_file', 'gdb', 'openocd', 'openocd_search']
# TODO: revisit whether we can get rid of some of these. Having
# tool-specific configuration options here is a layering
# violation, but it's very convenient to have a single place to
# store the locations of tools (like gdb and openocd) that are
# needed by multiple ZephyrBinaryRunner subclasses.
def __init__(self, build_dir, board_dir,
elf_file, hex_file, bin_file,
gdb=None, openocd=None, openocd_search=None):
self.build_dir = build_dir
'''Zephyr application build directory'''
self.board_dir = board_dir
'''Zephyr board directory'''
self.elf_file = elf_file
'''Path to the elf file that the runner should operate on'''
self.hex_file = hex_file
'''Path to the hex file that the runner should operate on'''
self.bin_file = bin_file
'''Path to the bin file that the runner should operate on'''
self.gdb = gdb
''''Path to GDB compatible with the target, may be None.'''
self.openocd = openocd
'''Path to OpenOCD to use for this target, may be None.'''
self.openocd_search = openocd_search
'''directory to add to OpenOCD search path, may be None.'''
_YN_CHOICES = ['Y', 'y', 'N', 'n', 'yes', 'no', 'YES', 'NO']
class _DTFlashAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values.lower().startswith('y'):
namespace.dt_flash = True
else:
namespace.dt_flash = False
class ZephyrBinaryRunner(abc.ABC):
'''Abstract superclass for binary runners (flashers, debuggers).
**Note**: these APIs are still evolving, and will change!
With some exceptions, boards supported by Zephyr must provide
generic means to be flashed (have a Zephyr firmware binary
permanently installed on the device for running) and debugged
(have a breakpoint debugger and program loader on a host
workstation attached to a running target).
This is supported by three top-level commands managed by the
Zephyr build system:
- 'flash': flash a previously configured binary to the board,
start execution on the target, then return.
- 'debug': connect to the board via a debugging protocol, program
the flash, then drop the user into a debugger interface with
symbol tables loaded from the current binary, and block until it
exits.
- 'debugserver': connect via a board-specific debugging protocol,
then reset and halt the target. Ensure the user is now able to
connect to a debug server with symbol tables loaded from the
binary.
- 'attach': connect to the board via a debugging protocol, then drop
the user into a debugger interface with symbol tables loaded from
the current binary, and block until it exits. Unlike 'debug', this
command does not program the flash.
This class provides an API for these commands. Every runner has a
name (like 'pyocd'), and declares commands it can handle (like
'flash'). Zephyr boards (like 'nrf52_pca10040') declare compatible
runner(s) by name to the build system, which makes concrete runner
instances to execute commands via this class.
If your board can use an existing runner, all you have to do is
give its name to the build system. How to do that is out of the
scope of this documentation, but use the existing boards as a
starting point.
If you want to define and use your own runner:
1. Define a ZephyrBinaryRunner subclass, and implement its
abstract methods. You may need to override capabilities().
2. Make sure the Python module defining your runner class is
imported, e.g. by editing this package's __init__.py (otherwise,
get_runners() won't work).
3. Give your runner's name to the Zephyr build system in your
board's build files.
For command-line invocation from the Zephyr build system, runners
define their own argparse-based interface through the common
add_parser() (and runner-specific do_add_parser() it delegates
to), and provide a way to create instances of themselves from
a RunnerConfig and parsed runner-specific arguments via create().
Runners use a variety of target-specific tools and configuration
values, the user interface to which is abstracted by this
class. Each runner subclass should take any values it needs to
execute one of these commands in its constructor. The actual
command execution is handled in the run() method.'''
def __init__(self, cfg):
'''Initialize core runner state.
`cfg` is a RunnerConfig instance.'''
self.cfg = cfg
@staticmethod
def get_runners():
'''Get a list of all currently defined runner classes.'''
return ZephyrBinaryRunner.__subclasses__()
@classmethod
@abc.abstractmethod
def name(cls):
'''Return this runner's user-visible name.
When choosing a name, pick something short and lowercase,
based on the name of the tool (like openocd, jlink, etc.) or
the target architecture/board (like xtensa, em-starterkit,
etc.).'''
@classmethod
def capabilities(cls):
'''Returns a RunnerCaps representing this runner's capabilities.
This implementation returns the default capabilities.
Subclasses should override appropriately if needed.'''
return RunnerCaps()
@classmethod
def add_parser(cls, parser):
'''Adds a sub-command parser for this runner.
The given object, parser, is a sub-command parser from the
argparse module. For more details, refer to the documentation
for argparse.ArgumentParser.add_subparsers().
The lone common optional argument is:
* --dt-flash (if the runner capabilities includes flash_addr)
Runner-specific options are added through the do_add_parser()
hook.'''
# Common options that depend on runner capabilities.
if cls.capabilities().flash_addr:
parser.add_argument('--dt-flash', default='n', choices=_YN_CHOICES,
action=_DTFlashAction,
help='''If 'yes', use configuration generated
by device tree (DT) to compute flash
addresses.''')
# Runner-specific options.
cls.do_add_parser(parser)
@classmethod
@abc.abstractmethod
def do_add_parser(cls, parser):
'''Hook for adding runner-specific options.'''
@classmethod
@abc.abstractmethod
def create(cls, cfg, args):
'''Create an instance from command-line arguments.
- `cfg`: RunnerConfig instance (pass to superclass __init__)
- `args`: runner-specific argument namespace parsed from
execution environment, as specified by `add_parser()`.'''
@classmethod
def get_flash_address(cls, args, build_conf, default=0x0):
'''Helper method for extracting a flash address.
If args.dt_flash is true, get the address from the
BoardConfiguration, build_conf. (If
CONFIG_HAS_FLASH_LOAD_OFFSET is n in that configuration, it
returns CONFIG_FLASH_BASE_ADDRESS. Otherwise, it returns
CONFIG_FLASH_BASE_ADDRESS + CONFIG_FLASH_LOAD_OFFSET.)
Otherwise (when args.dt_flash is False), the default value is
returned.'''
if args.dt_flash:
if build_conf['CONFIG_HAS_FLASH_LOAD_OFFSET']:
return (build_conf['CONFIG_FLASH_BASE_ADDRESS'] +
build_conf['CONFIG_FLASH_LOAD_OFFSET'])
else:
return build_conf['CONFIG_FLASH_BASE_ADDRESS']
else:
return default
def run(self, command, **kwargs):
'''Runs command ('flash', 'debug', 'debugserver', 'attach').
This is the main entry point to this runner.'''
caps = self.capabilities()
if command not in caps.commands:
raise ValueError('runner {} does not implement command {}'.format(
self.name(), command))
self.do_run(command, **kwargs)
@abc.abstractmethod
def do_run(self, command, **kwargs):
'''Concrete runner; run() delegates to this. Implement in subclasses.
In case of an unsupported command, raise a ValueError.'''
def run_server_and_client(self, server, client):
'''Run a server that ignores SIGINT, and a client that handles it.
This routine portably:
- creates a Popen object for the ``server`` command which ignores
SIGINT
- runs ``client`` in a subprocess while temporarily ignoring SIGINT
- cleans up the server after the client exits.
It's useful to e.g. open a GDB server and client.'''
server_proc = self.popen_ignore_int(server)
previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
self.check_call(client)
finally:
signal.signal(signal.SIGINT, previous)
server_proc.terminate()
server_proc.wait()
def call(self, cmd):
'''Subclass subprocess.call() wrapper.
Subclasses should use this method to run command in a
subprocess and get its return code, rather than
using subprocess directly, to keep accurate debug logs.
'''
quoted = quote_sh_list(cmd)
if JUST_PRINT:
log.inf(quoted)
return 0
log.dbg(quoted)
return subprocess.call(cmd)
def check_call(self, cmd):
'''Subclass subprocess.check_call() wrapper.
Subclasses should use this method to run command in a
subprocess and check that it executed correctly, rather than
using subprocess directly, to keep accurate debug logs.
'''
quoted = quote_sh_list(cmd)
if JUST_PRINT:
log.inf(quoted)
return
log.dbg(quoted)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
raise
def check_output(self, cmd):
'''Subclass subprocess.check_output() wrapper.
Subclasses should use this method to run command in a
subprocess and check that it executed correctly, rather than
using subprocess directly, to keep accurate debug logs.
'''
quoted = quote_sh_list(cmd)
if JUST_PRINT:
log.inf(quoted)
return b''
log.dbg(quoted)
try:
return subprocess.check_output(cmd)
except subprocess.CalledProcessError:
raise
def popen_ignore_int(self, cmd):
'''Spawn a child command, ensuring it ignores SIGINT.
The returned subprocess.Popen object must be manually terminated.'''
cflags = 0
preexec = None
system = platform.system()
quoted = quote_sh_list(cmd)
if system == 'Windows':
cflags |= subprocess.CREATE_NEW_PROCESS_GROUP
elif system in {'Linux', 'Darwin'}:
preexec = os.setsid
if JUST_PRINT:
log.inf(quoted)
return _DebugDummyPopen()
log.dbg(quoted)
return subprocess.Popen(cmd, creationflags=cflags, preexec_fn=preexec)

View file

@ -0,0 +1,122 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with dfu-util.'''
from collections import namedtuple
import sys
import time
from west import log
from runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
DfuSeConfig = namedtuple('DfuSeConfig', ['address', 'options'])
class DfuUtilBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for dfu-util.'''
def __init__(self, cfg, pid, alt, img, exe='dfu-util',
dfuse_config=None):
super(DfuUtilBinaryRunner, self).__init__(cfg)
self.alt = alt
self.img = img
self.cmd = [exe, '-d,{}'.format(pid)]
try:
self.list_pattern = ', alt={},'.format(int(self.alt))
except ValueError:
self.list_pattern = ', name="{}",'.format(self.alt)
if dfuse_config is None:
self.dfuse = False
else:
self.dfuse = True
self.dfuse_config = dfuse_config
@classmethod
def name(cls):
return 'dfu-util'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'}, flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
# Required:
parser.add_argument("--pid", required=True,
help="USB VID:PID of the board")
parser.add_argument("--alt", required=True,
help="interface alternate setting number or name")
# Optional:
parser.add_argument("--img",
help="binary to flash, default is --bin-file")
parser.add_argument("--dfuse", default=False, action='store_true',
help='''set if target is a DfuSe device;
implies --dt-flash.''')
parser.add_argument("--dfuse-modifiers", default='leave',
help='''colon-separated list of DfuSe modifiers
(default is "leave", which starts execution
immediately); --dfuse must also be given for this
option to take effect.''')
parser.add_argument('--dfu-util', default='dfu-util',
help='dfu-util executable; defaults to "dfu-util"')
@classmethod
def create(cls, cfg, args):
if args.img is None:
args.img = cfg.bin_file
if args.dfuse:
args.dt_flash = True # --dfuse implies --dt-flash.
build_conf = BuildConfiguration(cfg.build_dir)
dcfg = DfuSeConfig(address=cls.get_flash_address(args, build_conf),
options=args.dfuse_modifiers)
else:
dcfg = None
return DfuUtilBinaryRunner(cfg, args.pid, args.alt, args.img,
exe=args.dfu_util, dfuse_config=dcfg)
def find_device(self):
cmd = list(self.cmd) + ['-l']
output = self.check_output(cmd)
output = output.decode(sys.getdefaultencoding())
return self.list_pattern in output
def do_run(self, command, **kwargs):
reset = False
if not self.find_device():
reset = True
log.dbg('Device not found, waiting for it',
level=log.VERBOSE_EXTREME)
# Use of print() here is advised. We don't want to lose
# this information in a separate log -- this is
# interactive and requires a terminal.
print('Please reset your board to switch to DFU mode...')
while not self.find_device():
time.sleep(0.1)
cmd = list(self.cmd)
if self.dfuse:
# http://dfu-util.sourceforge.net/dfuse.html
dcfg = self.dfuse_config
addr_opts = hex(dcfg.address) + ':' + dcfg.options
cmd.extend(['-s', addr_opts])
cmd.extend(['-a', self.alt, '-D', self.img])
self.check_call(cmd)
if self.dfuse and 'leave' in dcfg.options.split(':'):
# Normal DFU devices generally need to be reset to switch
# back to the flashed program.
#
# DfuSe targets do as well, except when 'leave' is given
# as an option.
reset = False
if reset:
print('Now reset your board again to switch back to runtime mode.')

View file

@ -0,0 +1,101 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing ESP32 devices with esptool/espidf.'''
from os import path
from west import log
from runners.core import ZephyrBinaryRunner, RunnerCaps
class Esp32BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for espidf.'''
def __init__(self, cfg, device, baud=921600, flash_size='detect',
flash_freq='40m', flash_mode='dio', espidf='espidf',
bootloader_bin=None, partition_table_bin=None):
super(Esp32BinaryRunner, self).__init__(cfg)
self.elf = cfg.elf_file
self.device = device
self.baud = baud
self.flash_size = flash_size
self.flash_freq = flash_freq
self.flash_mode = flash_mode
self.espidf = espidf
self.bootloader_bin = bootloader_bin
self.partition_table_bin = partition_table_bin
@classmethod
def name(cls):
return 'esp32'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
# Required
parser.add_argument('--esp-idf-path', required=True,
help='path to ESP-IDF')
# Optional
parser.add_argument('--esp-device', default='/dev/ttyUSB0',
help='serial port to flash, default /dev/ttyUSB0')
parser.add_argument('--esp-baud-rate', default='921600',
help='serial baud rate, default 921600')
parser.add_argument('--esp-flash-size', default='detect',
help='flash size, default "detect"')
parser.add_argument('--esp-flash-freq', default='40m',
help='flash frequency, default "40m"')
parser.add_argument('--esp-flash-mode', default='dio',
help='flash mode, default "dio"')
parser.add_argument(
'--esp-tool',
help='''if given, complete path to espidf. default is to search for
it in [ESP_IDF_PATH]/components/esptool_py/esptool/esptool.py''')
parser.add_argument('--esp-flash-bootloader',
help='Bootloader image to flash')
parser.add_argument('--esp-flash-partition_table',
help='Partition table to flash')
@classmethod
def create(cls, cfg, args):
if args.esp_tool:
espidf = args.esp_tool
else:
espidf = path.join(args.esp_idf_path, 'components', 'esptool_py',
'esptool', 'esptool.py')
return Esp32BinaryRunner(
cfg, args.esp_device, baud=args.esp_baud_rate,
flash_size=args.esp_flash_size, flash_freq=args.esp_flash_freq,
flash_mode=args.esp_flash_mode, espidf=espidf,
bootloader_bin=args.esp_flash_bootloader,
partition_table_bin=args.esp_flash_partition_table)
def do_run(self, command, **kwargs):
bin_name = path.splitext(self.elf)[0] + path.extsep + 'bin'
cmd_convert = [self.espidf, '--chip', 'esp32', 'elf2image', self.elf]
cmd_flash = [self.espidf, '--chip', 'esp32', '--port', self.device,
'--baud', self.baud, '--before', 'default_reset',
'--after', 'hard_reset', 'write_flash', '-u',
'--flash_mode', self.flash_mode,
'--flash_freq', self.flash_freq,
'--flash_size', self.flash_size]
if self.bootloader_bin:
cmd_flash.extend(['0x1000', self.bootloader_bin])
cmd_flash.extend(['0x8000', self.partition_table_bin])
cmd_flash.extend(['0x10000', bin_name])
else:
cmd_flash.extend(['0x1000', bin_name])
log.inf("Converting ELF to BIN")
self.check_call(cmd_convert)
log.inf("Flashing ESP32 on {} ({}bps)".format(self.device, self.baud))
self.check_call(cmd_flash)

View file

@ -0,0 +1,167 @@
# Copyright (c) 2018 Intel Corporation.
# Copyright 2018 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging and flashing Intel S1000 CRB'''
from os import path
import time
import signal
from west import log
from runners.core import ZephyrBinaryRunner
DEFAULT_XT_GDB_PORT = 20000
class IntelS1000BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for Intel S1000.'''
def __init__(self, cfg, xt_ocd_dir,
ocd_topology, ocd_jtag_instr, gdb_flash_file,
gdb_port=DEFAULT_XT_GDB_PORT):
super(IntelS1000BinaryRunner, self).__init__(cfg)
self.board_dir = cfg.board_dir
self.elf_name = cfg.elf_file
self.gdb_cmd = cfg.gdb
self.xt_ocd_dir = xt_ocd_dir
self.ocd_topology = ocd_topology
self.ocd_jtag_instr = ocd_jtag_instr
self.gdb_flash_file = gdb_flash_file
self.gdb_port = gdb_port
@classmethod
def name(cls):
return 'intel_s1000'
@classmethod
def do_add_parser(cls, parser):
# Optional
parser.add_argument(
'--xt-ocd-dir', default='/opt/tensilica/xocd-12.0.4/xt-ocd',
help='ocd-dir, defaults to /opt/tensilica/xocd-12.0.4/xt-ocd')
parser.add_argument(
'--ocd-topology', default='topology_dsp0_flyswatter2.xml',
help='ocd-topology, defaults to topology_dsp0_flyswatter2.xml')
parser.add_argument(
'--ocd-jtag-instr', default='dsp0_gdb.txt',
help='ocd-jtag-instr, defaults to dsp0_gdb.txt')
parser.add_argument(
'--gdb-flash-file', default='load_elf.txt',
help='gdb-flash-file, defaults to load_elf.txt')
parser.add_argument(
'--gdb-port', default=DEFAULT_XT_GDB_PORT,
help='xt-gdb port, defaults to 20000')
@classmethod
def create(cls, cfg, args):
return IntelS1000BinaryRunner(
cfg, args.xt_ocd_dir,
args.ocd_topology, args.ocd_jtag_instr, args.gdb_flash_file,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
kwargs['ocd-topology'] = path.join(self.board_dir, 'support',
self.ocd_topology)
kwargs['ocd-jtag-instr'] = path.join(self.board_dir, 'support',
self.ocd_jtag_instr)
kwargs['gdb-flash-file'] = path.join(self.board_dir, 'support',
self.gdb_flash_file)
if command == 'flash':
self.flash(**kwargs)
elif command == 'debugserver':
self.debugserver(**kwargs)
else:
self.do_debug(**kwargs)
def flash(self, **kwargs):
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
gdb_flash_file = kwargs['gdb-flash-file']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Start the server
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
# Start the client
gdb_cmd = [self.gdb_cmd, '-x', gdb_flash_file]
client_proc = self.popen_ignore_int(gdb_cmd)
# Wait for 3 seconds (waiting for XTGDB to finish loading the image)
time.sleep(3)
# At this point, the ELF image is loaded and the program is in
# execution. Now we can quit the client (xt-gdb) and the server
# (xt-ocd) as they are not needed anymore. The loaded program
# (ELF) will continue to run though.
client_proc.terminate()
server_proc.terminate()
def do_debug(self, **kwargs):
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Start the server
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
gdb_cmd = [self.gdb_cmd,
'-ex', 'target remote :{}'.format(self.gdb_port),
self.elf_name]
# Start the client
# The below statement will consume the "^C" keypress ensuring
# the python main application doesn't exit. This is important
# since ^C in gdb means a "halt" operation.
previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
self.check_call(gdb_cmd)
finally:
signal.signal(signal.SIGINT, previous)
server_proc.terminate()
server_proc.wait()
def print_gdbserver_message(self, gdb_port):
log.inf('Intel S1000 GDB server running on port {}'.format(gdb_port))
def debugserver(self, **kwargs):
topology_file = kwargs['ocd-topology']
jtag_instr_file = kwargs['ocd-jtag-instr']
self.print_gdbserver_message(self.gdb_port)
server_cmd = [self.xt_ocd_dir,
'-c', topology_file,
'-I', jtag_instr_file]
# Note that XTOCD takes a few seconds to execute and always fails the
# first time. It has to be relaunched the second time to work.
server_proc = self.popen_ignore_int(server_cmd)
time.sleep(6)
server_proc.terminate()
self.check_call(server_cmd)

View file

@ -0,0 +1,150 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging with J-Link.'''
import os
import tempfile
import sys
from west import log
from runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
DEFAULT_JLINK_EXE = 'JLink.exe' if sys.platform == 'win32' else 'JLinkExe'
DEFAULT_JLINK_GDB_PORT = 2331
class JLinkBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the J-Link GDB server.'''
def __init__(self, cfg, device,
commander=DEFAULT_JLINK_EXE,
flash_addr=0x0, erase=True,
iface='swd', speed='auto',
gdbserver='JLinkGDBServer', gdb_port=DEFAULT_JLINK_GDB_PORT,
tui=False):
super(JLinkBinaryRunner, self).__init__(cfg)
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.device = device
self.commander = commander
self.flash_addr = flash_addr
self.erase = erase
self.gdbserver_cmd = [gdbserver]
self.iface = iface
self.speed = speed
self.gdb_port = gdb_port
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'jlink'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
# Required:
parser.add_argument('--device', required=True, help='device name')
# Optional:
parser.add_argument('--iface', default='swd',
help='interface to use, default is swd')
parser.add_argument('--speed', default='auto',
help='interface speed, default is autodetect')
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--gdbserver', default='JLinkGDBServer',
help='GDB server, default is JLinkGDBServer')
parser.add_argument('--gdb-port', default=DEFAULT_JLINK_GDB_PORT,
help='pyocd gdb port, defaults to {}'.format(
DEFAULT_JLINK_GDB_PORT))
parser.add_argument('--commander', default=DEFAULT_JLINK_EXE,
help='J-Link Commander, default is JLinkExe')
parser.add_argument('--erase', default=False, action='store_true',
help='if given, mass erase flash before loading')
@classmethod
def create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
return JLinkBinaryRunner(cfg, args.device,
commander=args.commander,
flash_addr=flash_addr, erase=args.erase,
iface=args.iface, speed=args.speed,
gdbserver=args.gdbserver,
gdb_port=args.gdb_port,
tui=args.tui)
def print_gdbserver_message(self):
log.inf('J-Link GDB server running on port {}'.format(self.gdb_port))
def do_run(self, command, **kwargs):
server_cmd = (self.gdbserver_cmd +
['-select', 'usb', # only USB connections supported
'-port', str(self.gdb_port),
'-if', self.iface,
'-speed', self.speed,
'-device', self.device,
'-silent',
'-singlerun'])
if command == 'flash':
self.flash(**kwargs)
elif command == 'debugserver':
self.print_gdbserver_message()
self.check_call(server_cmd)
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
client_cmd = (self.gdb_cmd +
self.tui_arg +
[self.elf_name] +
['-ex', 'target remote :{}'.format(self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
'-ex', 'monitor reset',
'-ex', 'load']
self.print_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)
def flash(self, **kwargs):
if self.bin_name is None:
raise ValueError('Cannot flash; bin_name is missing')
lines = ['r'] # Reset and halt the target
if self.erase:
lines.append('erase') # Erase all flash sectors
lines.append('loadfile {} 0x{:x}'.format(self.bin_name,
self.flash_addr))
lines.append('g') # Start the CPU
lines.append('q') # Close the connection and quit
log.dbg('JLink commander script:')
log.dbg('\n'.join(lines))
# Don't use NamedTemporaryFile: the resulting file can't be
# opened again on Windows.
with tempfile.TemporaryDirectory(suffix='jlink') as d:
fname = os.path.join(d, 'runner.jlink')
with open(fname, 'wb') as f:
f.writelines(bytes(line + '\n', 'utf-8') for line in lines)
cmd = ([self.commander] +
['-if', self.iface,
'-speed', self.speed,
'-device', self.device,
'-CommanderScript', fname])
log.inf('Flashing Target Device')
self.check_call(cmd)

View file

@ -0,0 +1,99 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for NIOS II, based on quartus-flash.py and GDB.'''
from west import log
from runners.core import ZephyrBinaryRunner, NetworkPortHelper
class Nios2BinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for NIOS II.'''
# From the original shell script:
#
# "XXX [flash] only support[s] cases where the .elf is sent
# over the JTAG and the CPU directly boots from __start. CONFIG_XIP
# and CONFIG_INCLUDE_RESET_VECTOR must be disabled."
def __init__(self, cfg, quartus_py=None, cpu_sof=None, tui=False):
super(Nios2BinaryRunner, self).__init__(cfg)
self.hex_name = cfg.hex_file
self.elf_name = cfg.elf_file
self.cpu_sof = cpu_sof
self.quartus_py = quartus_py
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'nios2'
@classmethod
def do_add_parser(cls, parser):
# TODO merge quartus-flash.py script into this file.
parser.add_argument('--quartus-flash', required=True)
parser.add_argument('--cpu-sof', required=True,
help='path to the the CPU .sof data')
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
@classmethod
def create(cls, cfg, args):
return Nios2BinaryRunner(cfg,
quartus_py=args.quartus_flash,
cpu_sof=args.cpu_sof,
tui=args.tui)
def do_run(self, command, **kwargs):
if command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
def flash(self, **kwargs):
if self.quartus_py is None:
raise ValueError('Cannot flash; --quartus-flash not given.')
if self.cpu_sof is None:
raise ValueError('Cannot flash; --cpu-sof not given.')
cmd = [self.quartus_py,
'--sof', self.cpu_sof,
'--kernel', self.hex_name]
self.check_call(cmd)
def print_gdbserver_message(self, gdb_port):
log.inf('Nios II GDB server running on port {}'.format(gdb_port))
def debug_debugserver(self, command, **kwargs):
# Per comments in the shell script, the NIOSII GDB server
# doesn't exit gracefully, so it's better to explicitly search
# for an unused port. The script picks a random value in
# between 1024 and 49151, but we'll start with the
# "traditional" 3333 choice.
gdb_start = 3333
nh = NetworkPortHelper()
gdb_port = nh.get_unused_ports([gdb_start])[0]
server_cmd = (['nios2-gdb-server',
'--tcpport', str(gdb_port),
'--stop', '--reset-target'])
if command == 'debugserver':
self.print_gdbserver_message(gdb_port)
self.check_call(server_cmd)
else:
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
gdb_cmd = (self.gdb_cmd +
self.tui_arg +
[self.elf_name,
'-ex', 'target remote :{}'.format(gdb_port)])
self.print_gdbserver_message(gdb_port)
self.run_server_and_client(server_cmd, gdb_cmd)

View file

@ -0,0 +1,130 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with nrfjprog.'''
import sys
from west import log
from runners.core import ZephyrBinaryRunner, RunnerCaps
class NrfJprogBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for nrfjprog.'''
def __init__(self, cfg, family, softreset, snr, erase=False):
super(NrfJprogBinaryRunner, self).__init__(cfg)
self.hex_ = cfg.hex_file
self.family = family
self.softreset = softreset
self.snr = snr
self.erase = erase
@classmethod
def name(cls):
return 'nrfjprog'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--nrf-family', required=True,
choices=['NRF51', 'NRF52'],
help='family of nRF MCU')
parser.add_argument('--softreset', required=False,
action='store_true',
help='use reset instead of pinreset')
parser.add_argument('--erase', action='store_true',
help='if given, mass erase flash before loading')
parser.add_argument('--snr', required=False,
help='serial number of board to use')
@classmethod
def create(cls, cfg, args):
return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
args.snr, erase=args.erase)
def get_board_snr_from_user(self):
snrs = self.check_output(['nrfjprog', '--ids'])
snrs = snrs.decode(sys.getdefaultencoding()).strip().splitlines()
if len(snrs) == 0:
raise RuntimeError('"nrfjprog --ids" did not find a board; '
'is the board connected?')
elif len(snrs) == 1:
board_snr = snrs[0]
if board_snr == '0':
raise RuntimeError('"nrfjprog --ids" returned 0; '
'is a debugger already connected?')
return board_snr
log.dbg("Refusing the temptation to guess a board",
level=log.VERBOSE_EXTREME)
# Use of print() here is advised. We don't want to lose
# this information in a separate log -- this is
# interactive and requires a terminal.
print('There are multiple boards connected.')
for i, snr in enumerate(snrs, 1):
print('{}. {}'.format(i, snr))
p = 'Please select one with desired serial number (1-{}): '.format(
len(snrs))
while True:
value = input(p)
try:
value = int(value)
except ValueError:
continue
if 1 <= value <= len(snrs):
break
return snrs[value - 1]
def do_run(self, command, **kwargs):
commands = []
if (self.snr is None):
board_snr = self.get_board_snr_from_user()
else:
board_snr = self.snr.lstrip("0")
program_cmd = ['nrfjprog', '--program', self.hex_, '-f', self.family,
'--snr', board_snr]
print('Flashing file: {}'.format(self.hex_))
if self.erase:
commands.extend([
['nrfjprog',
'--eraseall',
'-f', self.family,
'--snr', board_snr],
program_cmd
])
else:
if self.family == 'NRF51':
commands.append(program_cmd + ['--sectorerase'])
else:
commands.append(program_cmd + ['--sectoranduicrerase'])
if self.family == 'NRF52' and not self.softreset:
commands.extend([
# Enable pin reset
['nrfjprog', '--pinresetenable', '-f', self.family,
'--snr', board_snr],
])
if self.softreset:
commands.append(['nrfjprog', '--reset', '-f', self.family,
'--snr', board_snr])
else:
commands.append(['nrfjprog', '--pinreset', '-f', self.family,
'--snr', board_snr])
for cmd in commands:
self.check_call(cmd)
log.inf('Board with serial number {} flashed successfully.'.format(
board_snr))

View file

@ -0,0 +1,94 @@
# Copyright (c) 2018 Synopsys Inc.
# Copyright (c) 2017 Open Source Foundries Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''ARC architecture-specific runners.'''
from os import path
from runners.core import ZephyrBinaryRunner
DEFAULT_ARC_GDB_PORT = 3333
DEFAULT_PROPS_FILE = 'nsim.props'
class NsimBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for the ARC si.'''
# This unusual 'flash' implementation matches the original shell script.
#
# It works by starting a GDB server in a separate session, connecting a
# client to it to load the program, and running 'continue' within the
# client to execute the application.
#
def __init__(self, cfg,
tui=False,
gdb_port=DEFAULT_ARC_GDB_PORT,
props=DEFAULT_PROPS_FILE):
super(NsimBinaryRunner, self).__init__(cfg)
self.gdb_cmd = [cfg.gdb] + (['-tui'] if tui else [])
self.nsim_cmd = ['nsimdrv']
self.gdb_port = gdb_port
self.props = props
@classmethod
def name(cls):
return 'arc-nsim'
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--gdb-port', default=DEFAULT_ARC_GDB_PORT,
help='nsim gdb port, defaults to 3333')
parser.add_argument('--props', default=DEFAULT_PROPS_FILE,
help='nsim props file, defaults to nsim.props')
@classmethod
def create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')
return NsimBinaryRunner(
cfg,
gdb_port=args.gdb_port,
props=args.props)
def do_run(self, command, **kwargs):
kwargs['nsim-cfg'] = path.join(self.cfg.board_dir, 'support',
self.props)
if command == 'flash':
self.do_flash(**kwargs)
elif command == 'debug':
self.do_debug(**kwargs)
else:
self.debugserver(**kwargs)
def do_flash(self, **kwargs):
config = kwargs['nsim-cfg']
cmd = (self.nsim_cmd + ['-propsfile', config, self.cfg.elf_file])
self.check_call(cmd)
def do_debug(self, **kwargs):
config = kwargs['nsim-cfg']
server_cmd = (self.nsim_cmd + ['-gdb',
'-port={}'.format(self.gdb_port),
'-propsfile', config])
gdb_cmd = (self.gdb_cmd +
['-ex', 'target remote :{}'.format(self.gdb_port),
'-ex', 'load', self.cfg.elf_file])
self.run_server_and_client(server_cmd, gdb_cmd)
def debugserver(self, **kwargs):
config = kwargs['nsim-cfg']
cmd = (self.nsim_cmd +
['-gdb', '-port={}'.format(self.gdb_port),
'-propsfile', config])
self.check_call(cmd)

View file

@ -0,0 +1,145 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for openocd.'''
from os import path
from runners.core import ZephyrBinaryRunner
DEFAULT_OPENOCD_TCL_PORT = 6333
DEFAULT_OPENOCD_TELNET_PORT = 4444
DEFAULT_OPENOCD_GDB_PORT = 3333
class OpenOcdBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for openocd.'''
def __init__(self, cfg,
pre_cmd=None, load_cmd=None, verify_cmd=None, post_cmd=None,
tui=None,
tcl_port=DEFAULT_OPENOCD_TCL_PORT,
telnet_port=DEFAULT_OPENOCD_TELNET_PORT,
gdb_port=DEFAULT_OPENOCD_GDB_PORT):
super(OpenOcdBinaryRunner, self).__init__(cfg)
self.openocd_config = path.join(cfg.board_dir, 'support',
'openocd.cfg')
search_args = []
if cfg.openocd_search is not None:
search_args = ['-s', cfg.openocd_search]
self.openocd_cmd = [cfg.openocd] + search_args
self.elf_name = cfg.elf_file
self.load_cmd = load_cmd
self.verify_cmd = verify_cmd
self.pre_cmd = pre_cmd
self.post_cmd = post_cmd
self.tcl_port = tcl_port
self.telnet_port = telnet_port
self.gdb_port = gdb_port
self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
self.tui_arg = ['-tui'] if tui else []
@classmethod
def name(cls):
return 'openocd'
@classmethod
def do_add_parser(cls, parser):
# Options for flashing:
parser.add_argument('--cmd-pre-load',
help='Command to run before flashing')
parser.add_argument('--cmd-load',
help='''Command to load/flash binary
(required when flashing)''')
parser.add_argument('--cmd-verify',
help='''Command to verify flashed binary''')
parser.add_argument('--cmd-post-verify',
help='Command to run after verification')
# Options for debugging:
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--tcl-port', default=DEFAULT_OPENOCD_TCL_PORT,
help='openocd TCL port, defaults to 6333')
parser.add_argument('--telnet-port',
default=DEFAULT_OPENOCD_TELNET_PORT,
help='openocd telnet port, defaults to 4444')
parser.add_argument('--gdb-port', default=DEFAULT_OPENOCD_GDB_PORT,
help='openocd gdb port, defaults to 3333')
@classmethod
def create(cls, cfg, args):
return OpenOcdBinaryRunner(
cfg,
pre_cmd=args.cmd_pre_load, load_cmd=args.cmd_load,
verify_cmd=args.cmd_verify, post_cmd=args.cmd_post_verify,
tui=args.tui,
tcl_port=args.tcl_port, telnet_port=args.telnet_port,
gdb_port=args.gdb_port)
def do_run(self, command, **kwargs):
if command == 'flash':
self.do_flash(**kwargs)
elif command == 'debug':
self.do_debug(**kwargs)
else:
self.do_debugserver(**kwargs)
def do_flash(self, **kwargs):
if self.load_cmd is None:
raise ValueError('Cannot flash; load command is missing')
if self.verify_cmd is None:
raise ValueError('Cannot flash; verify command is missing')
pre_cmd = []
if self.pre_cmd is not None:
pre_cmd = ['-c', self.pre_cmd]
post_cmd = []
if self.post_cmd is not None:
post_cmd = ['-c', self.post_cmd]
cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'init',
'-c', 'targets'] +
pre_cmd +
['-c', 'reset halt',
'-c', self.load_cmd,
'-c', 'reset halt',
'-c', self.verify_cmd] +
post_cmd +
['-c', 'reset run',
'-c', 'shutdown'])
self.check_call(cmd)
def do_debug(self, **kwargs):
if self.gdb_cmd is None:
raise ValueError('Cannot debug; no gdb specified')
if self.elf_name is None:
raise ValueError('Cannot debug; no .elf specified')
server_cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'tcl_port {}'.format(self.tcl_port),
'-c', 'telnet_port {}'.format(self.telnet_port),
'-c', 'gdb_port {}'.format(self.gdb_port),
'-c', 'init',
'-c', 'targets',
'-c', 'halt'])
gdb_cmd = (self.gdb_cmd + self.tui_arg +
['-ex', 'target remote :{}'.format(self.gdb_port),
self.elf_name])
self.run_server_and_client(server_cmd, gdb_cmd)
def do_debugserver(self, **kwargs):
cmd = (self.openocd_cmd +
['-f', self.openocd_config,
'-c', 'init',
'-c', 'targets',
'-c', 'reset halt'])
self.check_call(cmd)

View file

@ -0,0 +1,170 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for pyOCD .'''
import os
from west import log
from runners.core import ZephyrBinaryRunner, RunnerCaps, \
BuildConfiguration
DEFAULT_PYOCD_GDB_PORT = 3333
class PyOcdBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for pyOCD.'''
def __init__(self, cfg, target,
flashtool='pyocd-flashtool', flash_addr=0x0,
flashtool_opts=None,
gdbserver='pyocd-gdbserver',
gdb_port=DEFAULT_PYOCD_GDB_PORT, tui=False,
board_id=None, daparg=None, frequency=None):
super(PyOcdBinaryRunner, self).__init__(cfg)
self.target_args = ['-t', target]
self.flashtool = flashtool
self.flash_addr_args = ['-a', hex(flash_addr)] if flash_addr else []
self.gdb_cmd = [cfg.gdb] if cfg.gdb is not None else None
self.gdbserver = gdbserver
self.gdb_port = gdb_port
self.tui_args = ['-tui'] if tui else []
self.hex_name = cfg.hex_file
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
board_args = []
if board_id is not None:
board_args = ['-b', board_id]
self.board_args = board_args
daparg_args = []
if daparg is not None:
daparg_args = ['-da', daparg]
self.daparg_args = daparg_args
frequency_args = []
if frequency is not None:
frequency_args = ['-f', frequency]
self.frequency_args = frequency_args
self.flashtool_extra = flashtool_opts if flashtool_opts else []
@classmethod
def name(cls):
return 'pyocd'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
flash_addr=True)
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--target', required=True,
help='target override')
parser.add_argument('--daparg',
help='Additional -da arguments to pyocd tool')
parser.add_argument('--flashtool', default='pyocd-flashtool',
help='flash tool path, default is pyocd-flashtool')
parser.add_argument('--flashtool-opt', default=[], action='append',
help='''Additional options for pyocd-flashtool,
e.g. -ce to chip erase''')
parser.add_argument('--frequency',
help='SWD clock frequency in Hz')
parser.add_argument('--gdbserver', default='pyocd-gdbserver',
help='GDB server, default is pyocd-gdbserver')
parser.add_argument('--gdb-port', default=DEFAULT_PYOCD_GDB_PORT,
help='pyocd gdb port, defaults to {}'.format(
DEFAULT_PYOCD_GDB_PORT))
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--board-id',
help='ID of board to flash, default is to prompt')
@classmethod
def create(cls, cfg, args):
daparg = os.environ.get('PYOCD_DAPARG')
if daparg:
log.wrn('Setting PYOCD_DAPARG in the environment is',
'deprecated; use the --daparg option instead.')
if args.daparg is None:
log.dbg('Missing --daparg set to {} from environment'.format(
daparg), level=log.VERBOSE_VERY)
args.daparg = daparg
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
return PyOcdBinaryRunner(
cfg, args.target, flashtool=args.flashtool,
flash_addr=flash_addr, flashtool_opts=args.flashtool_opt,
gdbserver=args.gdbserver, gdb_port=args.gdb_port, tui=args.tui,
board_id=args.board_id, daparg=args.daparg,
frequency=args.frequency)
def port_args(self):
return ['-p', str(self.gdb_port)]
def do_run(self, command, **kwargs):
if command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
def flash(self, **kwargs):
if os.path.isfile(self.hex_name):
fname = self.hex_name
elif os.path.isfile(self.bin_name):
fname = self.bin_name
else:
raise ValueError(
'Cannot flash; no hex ({}) or bin ({}) files'.format(
self.hex_name, self.bin_name))
cmd = ([self.flashtool] +
self.flash_addr_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.flashtool_extra +
[fname])
log.inf('Flashing Target Device')
self.check_call(cmd)
def print_gdbserver_message(self):
log.inf('pyOCD GDB server running on port {}'.format(self.gdb_port))
def debug_debugserver(self, command, **kwargs):
server_cmd = ([self.gdbserver] +
self.daparg_args +
self.port_args() +
self.target_args +
self.board_args +
self.frequency_args)
if command == 'debugserver':
self.print_gdbserver_message()
self.check_call(server_cmd)
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
client_cmd = (self.gdb_cmd +
self.tui_args +
[self.elf_name] +
['-ex', 'target remote :{}'.format(self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
'-ex', 'monitor reset',
'-ex', 'load']
self.print_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)

View file

@ -0,0 +1,34 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner stub for QEMU.'''
from runners.core import ZephyrBinaryRunner, RunnerCaps
class QemuBinaryRunner(ZephyrBinaryRunner):
'''Place-holder for QEMU runner customizations.'''
def __init__(self, cfg):
super(QemuBinaryRunner, self).__init__(cfg)
@classmethod
def name(cls):
return 'qemu'
@classmethod
def capabilities(cls):
# This is a stub.
return RunnerCaps(commands=set())
@classmethod
def do_add_parser(cls, parser):
pass # Nothing to do.
@classmethod
def create(cls, cfg, args):
return QemuBinaryRunner(cfg)
def do_run(self, command, **kwargs):
pass

View file

@ -0,0 +1,40 @@
# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for debugging with xt-gdb.'''
from os import path
from runners.core import ZephyrBinaryRunner, RunnerCaps
class XtensaBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for xt-gdb.'''
def __init__(self, cfg):
super(XtensaBinaryRunner, self).__init__(cfg)
@classmethod
def name(cls):
return 'xtensa'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'debug'})
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--xcc-tools', required=True,
help='path to XTensa tools')
@classmethod
def create(cls, cfg, args):
# Override any GDB with the one provided by the XTensa tools.
cfg.gdb = path.join(args.xcc_tools, 'bin', 'xt-gdb')
return XtensaBinaryRunner(cfg)
def do_run(self, command, **kwargs):
gdb_cmd = [self.cfg.gdb, self.cfg.elf_file]
self.check_call(gdb_cmd)