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:
parent
6aa87b6f5b
commit
ab82264ace
|
@ -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:
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -5,6 +5,4 @@
|
|||
West APIs
|
||||
#########
|
||||
|
||||
.. automodule:: west.runners.core
|
||||
:members:
|
||||
|
||||
|
|
|
@ -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
17
scripts/west-commands.yml
Normal 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
|
11
scripts/west_commands/README.txt
Normal file
11
scripts/west_commands/README.txt
Normal 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!
|
290
scripts/west_commands/build.py
Normal file
290
scripts/west_commands/build.py
Normal 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)
|
75
scripts/west_commands/debug.py
Normal file
75
scripts/west_commands/debug.py
Normal 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')
|
31
scripts/west_commands/flash.py
Normal file
31
scripts/west_commands/flash.py
Normal 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')
|
454
scripts/west_commands/run_common.py
Normal file
454
scripts/west_commands/run_common.py
Normal 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)
|
35
scripts/west_commands/runners/__init__.py
Normal file
35
scripts/west_commands/runners/__init__.py
Normal 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']
|
107
scripts/west_commands/runners/arc.py
Normal file
107
scripts/west_commands/runners/arc.py
Normal 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)
|
96
scripts/west_commands/runners/blackmagicprobe.py
Normal file
96
scripts/west_commands/runners/blackmagicprobe.py
Normal 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)
|
54
scripts/west_commands/runners/bossac.py
Normal file
54
scripts/west_commands/runners/bossac.py
Normal 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)
|
508
scripts/west_commands/runners/core.py
Normal file
508
scripts/west_commands/runners/core.py
Normal 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)
|
122
scripts/west_commands/runners/dfu.py
Normal file
122
scripts/west_commands/runners/dfu.py
Normal 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.')
|
101
scripts/west_commands/runners/esp32.py
Normal file
101
scripts/west_commands/runners/esp32.py
Normal 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)
|
167
scripts/west_commands/runners/intel_s1000.py
Normal file
167
scripts/west_commands/runners/intel_s1000.py
Normal 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)
|
150
scripts/west_commands/runners/jlink.py
Normal file
150
scripts/west_commands/runners/jlink.py
Normal 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)
|
99
scripts/west_commands/runners/nios2.py
Normal file
99
scripts/west_commands/runners/nios2.py
Normal 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)
|
130
scripts/west_commands/runners/nrfjprog.py
Normal file
130
scripts/west_commands/runners/nrfjprog.py
Normal 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))
|
94
scripts/west_commands/runners/nsim.py
Normal file
94
scripts/west_commands/runners/nsim.py
Normal 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)
|
145
scripts/west_commands/runners/openocd.py
Normal file
145
scripts/west_commands/runners/openocd.py
Normal 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)
|
170
scripts/west_commands/runners/pyocd.py
Normal file
170
scripts/west_commands/runners/pyocd.py
Normal 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)
|
34
scripts/west_commands/runners/qemu.py
Normal file
34
scripts/west_commands/runners/qemu.py
Normal 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
|
40
scripts/west_commands/runners/xtensa.py
Normal file
40
scripts/west_commands/runners/xtensa.py
Normal 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)
|
Loading…
Reference in a new issue