Skip to content

Add support for cbuild-run.yml #1771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/target_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,19 @@ _Note:_ PyOCD can work with expanded packs just like zipped .pack files. Pass th
of the pack using the `--pack` argument, as above. This is very useful for cases such as development or
debugging of a pack, or for working with other CMSIS-Pack managers that store packs in decompressed form.

#### CMSIS-Toolbox Run and Debug Management

Run and Debug Management is a part of the [CMSIS-Toolbox](https://github.com/Open-CMSIS-Pack/cmsis-toolbox) build
information files when working with CSolution projects. Each `*.cbuild-run.yml` file contains relevant run and
debug information for a single `target-type` defined in a CSolution project. The tool gathers and filters the
target information from the BSP and DFP.

To use the `cbuild-run` target information in PyOCD, pass the `--cbuild-run` option followed by the path to
desired `*.cbuild-run.yml` file. The target information is not cached in PyOCD, which means the command-line
option should always be passed when launching the debugger.

Because a single `target-type` is described in the YAML file, the `--target`
command-line argument is not needed, as the target is automatically selected.

More information on CMSIS-Toolbox and the CSolution project format for Run and Debug Management can be found in the
[CMSIS-Toolbox Documentation](https://open-cmsis-pack.github.io/cmsis-toolbox/YML-CBuild-Format/#run-and-debug-management).
17 changes: 15 additions & 2 deletions pyocd/board/board.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyOCD debugger
# Copyright (c) 2006-2013,2018 Arm Limited
# Copyright (c) 2006-2013,2018,2025 Arm Limited
# Copyright (c) 2021-2022 Chris Reed
# Copyright (c) 2023 Benjamin Sølberg
# SPDX-License-Identifier: Apache-2.0
Expand All @@ -22,6 +22,7 @@
from ..core import exceptions
from ..target import (TARGET, normalise_target_type_name)
from ..target.pack import pack_target
from ..target.pack.cbuild_run import CbuildRun
from ..utility.graph import GraphNode

if TYPE_CHECKING:
Expand Down Expand Up @@ -63,10 +64,18 @@ def __init__(self,
"""
super().__init__()

# Create cbuild_run if option is provided
if session.options.is_set('cbuild_run'):
cbuild_run = CbuildRun(session.options.get('cbuild_run'))
else:
cbuild_run = None

# Use the session option if no target type was given to us.
if target is None:
if session.options.is_set('target_override'):
target = session.options.get('target_override')
elif cbuild_run and cbuild_run.target:
target = cbuild_run.target
elif board_info:
target = board_info.target

Expand Down Expand Up @@ -99,11 +108,15 @@ def __init__(self,
self._delegate = None
self._inited = False

# Create target from cbuild-run information.
if cbuild_run and cbuild_run.target:
cbuild_run.populate_target(target)

# Create targets from provided CMSIS pack.
if session.options['pack'] is not None:
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])

# Create targets from the cmsis-pack-manager cache.
# Create target from the cmsis-pack-manager cache.
if self._target_type not in TARGET:
pack_target.ManagedPacks.populate_target(target)

Expand Down
21 changes: 16 additions & 5 deletions pyocd/coresight/ap.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyOCD debugger
# Copyright (c) 2015-2020 Arm Limited
# Copyright (c) 2015-2020,2025 Arm Limited
# Copyright (c) 2021-2023 Chris Reed
# SPDX-License-Identifier: Apache-2.0
#
Expand Down Expand Up @@ -206,10 +206,11 @@ class APAddressBase:
address format.
"""

def __init__(self, address: int, dp: int = 0) -> None:
def __init__(self, address: int, dp: int = 0, apid: int = 0) -> None:
"""@brief Constructor accepting the nominal address."""
self._nominal_address = address
self._dp = dp
self._apid = apid

@property
def ap_version(self) -> APVersion:
Expand Down Expand Up @@ -241,6 +242,11 @@ def dp_index(self) -> int:
"""@brief Index of the DP to which this AP is attached."""
return self._dp

@property
def apid(self) -> int:
"""@brief Unique AP identifier."""
return self._apid

def __hash__(self) -> int:
return hash(self.nominal_address | (self._dp << 64))

Expand Down Expand Up @@ -788,6 +794,10 @@ def _init_hprot() -> None:
LOG.debug("%s implemented HPROT=%x HNONSEC=%x", self.short_description, self._impl_hprot,
self._impl_hnonsec)

if (self._flags & AP_SPROT):
if (original_csw & CSW_SDEVICEEN) == 0:
self._hnonsec = NONSECURE

# Update current HPROT and HNONSEC, and the current base CSW value.
self.hprot = self._hprot & self._impl_hprot
self.hnonsec = self._hnonsec & self._impl_hnonsec
Expand Down Expand Up @@ -1355,6 +1365,7 @@ def find_components(self) -> None:
AP_ALL_TX_SZ = 0x2 # The AP is known to support 8-, 16-, and 32-bit transfers, *unless* Large Data is implemented.
AP_MSTRTYPE = 0x4 # The AP is known to support the MSTRTYPE field.
AP_DBGSWEN = 0x8 # The AP is known to support the DBGSWEN flag.
AP_SPROT = 0x10 # The AP is known to support the SPROT field.

## Map from AP IDR fields to AccessPort subclass.
#
Expand All @@ -1380,20 +1391,20 @@ def find_components(self) -> None:
# |JEP106 |Class |Var|Type |Name |Class
(AP_JEP106_ARM, AP_CLASS_JTAG_AP, 0, 0): ("JTAG-AP", AccessPort, 0 ),
(AP_JEP106_ARM, AP_CLASS_COM_AP, 0, 0): ("SDC-600", AccessPort, 0 ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ|AP_SPROT ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 1, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ|AP_4K_WRAP|AP_MSTRTYPE ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 2, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 3, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 4, AP_TYPE_AHB): ("AHB-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_APB): ("APB-AP", MEM_AP, AP_DBGSWEN ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AXI): ("AXI-AP", MEM_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB5): ("AHB5-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB5): ("AHB5-AP", AHB_AP, AP_ALL_TX_SZ|AP_SPROT ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 1, AP_TYPE_AHB5): ("AHB5-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 2, AP_TYPE_AHB5): ("AHB5-AP", AHB_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_APB4): ("APB4-AP", MEM_AP, 0 ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AXI5): ("AXI5-AP", MEM_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 1, AP_TYPE_AXI5): ("AXI5-AP", MEM_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB5_HPROT): ("AHB5-AP", MEM_AP, AP_ALL_TX_SZ ),
(AP_JEP106_ARM, AP_CLASS_MEM_AP, 0, AP_TYPE_AHB5_HPROT): ("AHB5-AP", MEM_AP, AP_ALL_TX_SZ|AP_SPROT ),
(AP_JEP106_ARM_CHINA,
AP_CLASS_MEM_AP, 1, AP_TYPE_AHB5): ("AHB5-AP", AHB_AP, AP_ALL_TX_SZ ),
}
7 changes: 4 additions & 3 deletions pyocd/coresight/discovery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyOCD debugger
# Copyright (c) 2019-2020 Arm Limited
# Copyright (c) 2019-2020,2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -258,8 +258,9 @@ def _create_root_component(self, cmpid):
"""
try:
# Create a memory interface for this component.
ap_address = APv2Address(cmpid.address)
memif = APAccessMemoryInterface(self.dp, ap_address)
# ap_address = APv2Address(cmpid.address)
# memif = APAccessMemoryInterface(self.dp, ap_address)
memif = APAccessMemoryInterface(self.dp)

# Instantiate the component and attach to the target.
component = cmpid.factory(memif, cmpid, cmpid.address)
Expand Down
4 changes: 2 additions & 2 deletions pyocd/debug/sequences/sequences.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pyOCD debugger
# Copyright (c) 2020 Arm Limited
# Copyright (c) 2020,2025 Arm Limited
# Copyright (c) 2021-2023 Chris Reed
# SPDX-License-Identifier: Apache-2.0
#
Expand Down Expand Up @@ -380,7 +380,7 @@ def _create_scope(self, context: DebugSequenceExecutionContext) -> Scope:
scope.set('__ap', default_ap_address.nominal_address
if isinstance(default_ap_address, APv1Address)
else 0)
scope.set('__apid', default_ap_address.nominal_address
scope.set('__apid', default_ap_address.apid
if isinstance(default_ap_address, APv2Address)
else 0)

Expand Down
14 changes: 9 additions & 5 deletions pyocd/gdbserver/gdbserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class GDBServer(threading.Thread):
## Timer delay for sending the notification that the server is listening.
START_LISTENING_NOTIFY_DELAY = 0.03 # 30 ms

def __init__(self, session, core=None):
def __init__(self, session, core=None, port=None):
super().__init__()
self.session = session
self.board = session.board
Expand All @@ -120,9 +120,13 @@ def __init__(self, session, core=None):
self.target = self.board.target.cores[core]
self.name = "gdb-server-core%d" % self.core

self.port = session.options.get('gdbserver_port')
if self.port != 0:
self.port += self.core
if port is None:
self.port = session.options.get('gdbserver_port')
if self.port != 0:
self.port += self.core
else:
self.port = port

self.telnet_port = session.options.get('telnet_port')
if self.telnet_port != 0:
self.telnet_port += self.core
Expand Down Expand Up @@ -328,7 +332,7 @@ def run(self):

while not self.shutdown_event.is_set():
connected = self.abstract_socket.connect()
if connected != None:
if connected is not None:
self.packet_io = GDBServerPacketIOThread(self.abstract_socket)
break

Expand Down
3 changes: 3 additions & 0 deletions pyocd/subcommands/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021-2023 Chris Reed
# Copyright (c) 2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -68,6 +69,8 @@ class CommonOptions:
help="(Deprecated) Send setting to DAPAccess layer.")
CONFIG_GROUP.add_argument("--pack", metavar="PATH", action="append",
help="Path to the .pack file for a CMSIS Device Family Pack.")
CONFIG_GROUP.add_argument("--cbuild-run", metavar="PATH",
help="Path to the .cbuild-run.yml file for CSolution Run and Debug Management.")

# Define common options for all subcommands, including logging options.
COMMON = argparse.ArgumentParser(description='common',
Expand Down
4 changes: 2 additions & 2 deletions pyocd/subcommands/erase_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021 Chris Reed
# Copyright (c) 2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -80,6 +81,7 @@ def invoke(self) -> int:
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,
Expand All @@ -99,5 +101,3 @@ def invoke(self) -> int:
eraser.erase(addresses)

return 0


14 changes: 13 additions & 1 deletion pyocd/subcommands/gdbserver_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021 Chris Reed
# Copyright (c) 2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -91,6 +92,8 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
help="Run command (OpenOCD compatibility).")
gdbserver_options.add_argument("-bh", "--soft-bkpt-as-hard", dest="soft_bkpt_as_hard", default=False, action="store_true",
help="Replace software breakpoints with hardware breakpoints.")
gdbserver_options.add_argument("--reset-run", action="store_true",
help="Reset and run before running GDB server")

return [cls.CommonOptions.COMMON, cls.CommonOptions.CONNECT, gdbserver_parser]

Expand Down Expand Up @@ -182,6 +185,7 @@ def invoke(self) -> int:
config_file=self._args.config,
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,
Expand Down Expand Up @@ -215,6 +219,10 @@ def invoke(self) -> int:
session.probeserver = probe_server
probe_server.start()

# Reset and run the target
if self._args.reset_run:
session.board.target.reset()

# Start up the gdbservers.
for core_number, core in session.board.target.cores.items():
# Don't create a server for CPU-less memory Access Port.
Expand All @@ -223,7 +231,11 @@ def invoke(self) -> int:
# Don't create a server if this core is not listed by the user.
if core_number not in core_list:
continue
gdb = GDBServer(session, core=core_number)
# Read pname and port mapping from cbuild-run.
port_number = None
if self._args.cbuild_run:
port_number = session.target.get_gdbserver_port(core.node_name)
gdb = GDBServer(session, core=core_number, port=port_number)
# Only subscribe to the server for the first core, so echo messages aren't printed
# multiple times.
if not gdbs:
Expand Down
8 changes: 6 additions & 2 deletions pyocd/subcommands/json_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021 Chris Reed
# Copyright (c) 2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -23,7 +24,7 @@
from .base import SubcommandBase
from ..core.session import Session
from ..tools.lists import ListGenerator
from ..target.pack import pack_target
from ..target.pack import pack_target, cbuild_run
from ..utility.cmdline import convert_session_options
from .. import __version__

Expand Down Expand Up @@ -92,13 +93,17 @@ def invoke(self) -> int:
config_file=self._args.config,
no_config=self._args.no_config,
pack=self._args.pack,
cbuild_run=self._args.cbuild_run,
**convert_session_options(self._args.options)
)

if self._args.targets or self._args.boards:
# Create targets from provided CMSIS pack.
if session.options['pack'] is not None:
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])
# Create target from provided CbuildRun file.
if session.options['cbuild_run'] is not None:
cbuild_run.CbuildRun(session.options['cbuild_run']).populate_target()

if self._args.probes:
obj = ListGenerator.list_probes()
Expand All @@ -122,4 +127,3 @@ def invoke(self) -> int:

print(json.dumps(obj, indent=4))
return exit_status

10 changes: 7 additions & 3 deletions pyocd/subcommands/list_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021 Chris Reed
# Copyright (c) 2025 Arm Limited
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -22,7 +23,7 @@
from ..core.helpers import ConnectHelper
from ..core.session import Session
from ..tools.lists import ListGenerator
from ..target.pack import pack_target
from ..target.pack import pack_target, cbuild_run
from ..utility.cmdline import convert_session_options

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -60,7 +61,7 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
help="Restrict listing to items matching the given name substring. Applies to targets and boards.")
list_options.add_argument('-r', '--vendor',
help="Restrict listing to items whose vendor matches the given name substring. Applies only to targets.")
list_options.add_argument('-s', '--source', choices=('builtin', 'pack'),
list_options.add_argument('-s', '--source', choices=('builtin', 'pack', 'cbuild-run'),
help="Restrict listing to targets from the specified source. Applies to targets.")
list_options.add_argument('-H', '--no-header', action='store_true',
help="Don't print a table header.")
Expand All @@ -87,6 +88,7 @@ def invoke(self) -> int:
config_file=self._args.config,
no_config=self._args.no_config,
pack=self._args.pack,
cbuild_run=self._args.cbuild_run,
**convert_session_options(self._args.options)
)

Expand All @@ -96,6 +98,9 @@ def invoke(self) -> int:
# Create targets from provided CMSIS pack.
if session.options['pack'] is not None:
pack_target.PackTargets.populate_targets_from_pack(session.options['pack'])
# Create target from provided CbuildRun file.
if session.options['cbuild_run'] is not None:
cbuild_run.CbuildRun(session.options['cbuild_run']).populate_target()

obj = ListGenerator.list_targets(name_filter=self._args.name,
vendor_filter=self._args.vendor,
Expand Down Expand Up @@ -135,4 +140,3 @@ def invoke(self) -> int:
print(pt)

return 0

Loading
Loading