From 681cb4b5933266c4d5c91f2e6ba56e7c6731c2f9 Mon Sep 17 00:00:00 2001 From: Robert Rostohar Date: Mon, 28 Sep 2026 12:03:27 +0200 Subject: [PATCH 1/2] Add pyocd info sub-command --- pyocd/__main__.py | 4 +- pyocd/subcommands/info_cmd.py | 124 ++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 pyocd/subcommands/info_cmd.py diff --git a/pyocd/__main__.py b/pyocd/__main__.py index 00bfb0a01..fe605be7f 100644 --- a/pyocd/__main__.py +++ b/pyocd/__main__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # pyOCD debugger -# Copyright (c) 2018-2020 Arm Limited +# Copyright (c) 2018-2020,2026 Arm Limited # Copyright (c) 2020 Cypress Semiconductor Corporation # Copyright (c) 2021-2022 Chris Reed # SPDX-License-Identifier: Apache-2.0 @@ -36,6 +36,7 @@ from .subcommands.commander_cmd import CommanderSubcommand from .subcommands.erase_cmd import EraseSubcommand from .subcommands.gdbserver_cmd import GdbserverSubcommand +from .subcommands.info_cmd import InfoSubcommand from .subcommands.json_cmd import JsonSubcommand from .subcommands.list_cmd import ListSubcommand from .subcommands.load_cmd import LoadSubcommand @@ -60,6 +61,7 @@ class PyOCDTool(SubcommandBase): EraseSubcommand, LoadSubcommand, GdbserverSubcommand, + InfoSubcommand, JsonSubcommand, ListSubcommand, PackSubcommand, diff --git a/pyocd/subcommands/info_cmd.py b/pyocd/subcommands/info_cmd.py new file mode 100644 index 000000000..1bfc9052f --- /dev/null +++ b/pyocd/subcommands/info_cmd.py @@ -0,0 +1,124 @@ +# pyOCD debugger +# Copyright (c) 2026 Arm Limited +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import logging +from typing import (Any, List) + +from .base import SubcommandBase +from ..core.helpers import ConnectHelper +from ..coresight.core_ids import CortexMExtension +from ..utility.cmdline import convert_session_options + +LOG = logging.getLogger(__name__) + + +class InfoSubcommand(SubcommandBase): + """@brief `pyocd info` subcommand.""" + + NAMES = ['info'] + HELP = "Display information about the connected target." + DEFAULT_LOG_LEVEL = logging.WARNING + + @classmethod + def get_args(cls) -> List[argparse.ArgumentParser]: + """@brief Add this subcommand to the subparsers object.""" + info_parser = argparse.ArgumentParser(description='info', add_help=False) + return [cls.CommonOptions.COMMON, cls.CommonOptions.CONNECT, info_parser] + + @staticmethod + def _print_core(core: Any, show_core_number: bool) -> None: + """@brief Display the information and detected features of a core.""" + core_label = "CPU core" + if show_core_number: + core_label += f" #{core.core_number}" + core_info = f"{core_label}: {getattr(core, 'name', 'Unknown')}" + revision = getattr(core, 'cpu_revision', None) + patch = getattr(core, 'cpu_patch', None) + if revision is not None and patch is not None: + core_info += f" r{revision}p{patch}" + architecture_version = getattr(core, 'architecture_version', None) + if architecture_version is not None: + major, minor = architecture_version + core_info += f", v{major}.{minor}-M architecture" + print(core_info) + + extensions = getattr(core, 'extensions', []) + if extensions: + print(f" Extensions: {', '.join(sorted(x.name for x in extensions))}") + + fpb = getattr(core, 'fpb', None) + if fpb is not None: + print(f" {fpb.nb_code} hardware breakpoints, {fpb.nb_lit} literal comparators") + + dwt = getattr(core, 'dwt', None) + if dwt is not None: + print(f" {dwt.watchpoint_count} hardware watchpoints") + + def invoke(self) -> int: + """@brief Handle the 'info' subcommand.""" + session = ConnectHelper.session_with_chosen_probe( + project_dir=self._args.project_dir, + config_file=self._args.config, + user_script=self._args.script, + no_config=self._args.no_config, + pack=self._args.pack, + cbuild_run=self._args.cbuild_run, + unique_id=self._args.unique_id, + target_override=self._args.target_override, + frequency=self._args.frequency, + blocking=False, + connect_mode=self._args.connect_mode, + command=self._args.cmd, + options=convert_session_options( + self._args.options), + option_defaults=self._modified_option_defaults(), + ) + if session is None: + LOG.error("No target device available") + return 1 + + try: + # Initialising the board performs DP/AP and ROM table discovery, + # which provides the target and core information reported below. + session.open() + assert session.probe + assert session.target + + print( + f"Debug Probe: {session.probe.description} " + f"[{session.probe.unique_id}]") + + protocol = session.probe.wire_protocol + frequency = session.options.get('frequency') + if frequency >= 1000000: + frequency_text = f"{frequency / 1000000:g}MHz" + elif frequency >= 1000: + frequency_text = f"{frequency / 1000:g}kHz" + else: + frequency_text = f"{frequency:g}Hz" + print(f"Debug Port: {protocol.name} [{frequency_text}]") + + cores = session.target.cores + if not cores: + print("CPU Core: None discovered") + else: + for core in cores.values(): + self._print_core(core, show_core_number=(len(cores) > 1)) + finally: + session.close() + + return 0 From 78f71a97f56cc45b651d6d165f28281ec173d20d Mon Sep 17 00:00:00 2001 From: Robert Rostohar Date: Tue, 6 Oct 2026 13:29:57 +0200 Subject: [PATCH 2/2] Update pyocd info sub-command --- README.md | 5 +++-- pyocd/subcommands/info_cmd.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e0a0da5f7..be5447fda 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,15 @@ The `pyocd` command line tool gives you total control over your device with thes [several GUI debugger options](https://pyocd.io/docs/gdb_setup). - `load`: Program files of various formats into flash or RAM. - `erase`: Erase part or all of an MCU's flash memory. +- `run`: Load and run the target. +- `reset`: Hardware or software reset of a device. +- `info`: Display information about the connected target. - `pack`: Manage [CMSIS Device Family Packs](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/index.html) that provide additional target device support. - `commander`: Interactive REPL control and inspection of the MCU. - `server`: Share a debug probe with a TCP/IP server. -- `reset`: Hardware or software reset of a device. - `rtt`: Stream Segger RTT IO with _any_ debug probe. - `list`: Show connected devices. -- `run`: Load and run the target. The API and tools provide these features: diff --git a/pyocd/subcommands/info_cmd.py b/pyocd/subcommands/info_cmd.py index 1bfc9052f..dfd835c48 100644 --- a/pyocd/subcommands/info_cmd.py +++ b/pyocd/subcommands/info_cmd.py @@ -16,11 +16,11 @@ import argparse import logging -from typing import (Any, List) +from typing import List from .base import SubcommandBase from ..core.helpers import ConnectHelper -from ..coresight.core_ids import CortexMExtension +from ..core.core_target import CoreTarget from ..utility.cmdline import convert_session_options LOG = logging.getLogger(__name__) @@ -40,12 +40,12 @@ def get_args(cls) -> List[argparse.ArgumentParser]: return [cls.CommonOptions.COMMON, cls.CommonOptions.CONNECT, info_parser] @staticmethod - def _print_core(core: Any, show_core_number: bool) -> None: + def _print_core(core: CoreTarget, show_core_number: bool) -> None: """@brief Display the information and detected features of a core.""" core_label = "CPU core" if show_core_number: core_label += f" #{core.core_number}" - core_info = f"{core_label}: {getattr(core, 'name', 'Unknown')}" + core_info = f"{core_label}: {core.name}" revision = getattr(core, 'cpu_revision', None) patch = getattr(core, 'cpu_patch', None) if revision is not None and patch is not None: @@ -83,8 +83,7 @@ def invoke(self) -> int: blocking=False, connect_mode=self._args.connect_mode, command=self._args.cmd, - options=convert_session_options( - self._args.options), + options=convert_session_options(self._args.options), option_defaults=self._modified_option_defaults(), ) if session is None: @@ -98,9 +97,7 @@ def invoke(self) -> int: assert session.probe assert session.target - print( - f"Debug Probe: {session.probe.description} " - f"[{session.probe.unique_id}]") + print(f"Debug Probe: {session.probe.description} [{session.probe.unique_id}]") protocol = session.probe.wire_protocol frequency = session.options.get('frequency')