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

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
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
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


2 changes: 2 additions & 0 deletions 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 @@ -182,6 +183,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
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

17 changes: 13 additions & 4 deletions pyocd/subcommands/load_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pyOCD debugger
# Copyright (c) 2021-2022 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 @@ -65,9 +66,9 @@ def get_args(cls) -> List[argparse.ArgumentParser]:
parser_options.add_argument("--no-reset", action="store_true",
help="Specify to prevent resetting device after programming has finished.")

parser.add_argument("file", metavar="<file-path>", nargs="+",
parser.add_argument("file", metavar="<file-path>", nargs="*",
help="File to write to memory. Binary files can have an optional base address appended to the file "
"name as '@<address>', for instance 'app.bin@0x20000'.")
"name as '@<address>', for instance 'app.bin@0x20000'. Optional if '--cbuild-run' is used.")

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

Expand All @@ -76,6 +77,9 @@ def invoke(self) -> int:
self._increase_logging(["pyocd.flash.loader", __name__])

# Validate arguments.
if (self._args.cbuild_run is None) and not self._args.file:
raise ValueError("Positional argument <file-path> is required when '--cbuild-run' is not used.")

if (self._args.base_address is not None) and (len(self._args.file) > 1):
raise ValueError("--base-address cannot be set when loading more than one file; "
"use a base address suffix instead")
Expand All @@ -86,6 +90,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 @@ -102,6 +107,10 @@ def invoke(self) -> int:
chip_erase=self._args.erase,
trust_crc=self._args.trust_crc,
no_reset=self._args.no_reset)
if not self._args.file and self._args.cbuild_run:
# Populate file list from cbuild-run output if not provided explicitly
cbuild_files = session.target._cbuild_device.output
self._args.file = cbuild_files.keys()
for filename in self._args.file:
# Get an initial path with the argument as-is.
file_path = Path(filename).expanduser()
Expand All @@ -117,6 +126,8 @@ def invoke(self) -> int:
return 1
else:
base_address = self._args.base_address
if base_address is None and self._args.cbuild_run:
base_address = cbuild_files[filename]

# Resolve our path.
file_path = Path(filename).expanduser().resolve()
Expand All @@ -133,5 +144,3 @@ def invoke(self) -> int:
file_format=self._args.format)

return 0


Loading
Loading