openocd-python 2026.2.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openocd/__init__.py +64 -0
- openocd/breakpoints.py +234 -0
- openocd/cli.py +161 -0
- openocd/connection/__init__.py +7 -0
- openocd/connection/base.py +30 -0
- openocd/connection/tcl_rpc.py +259 -0
- openocd/connection/telnet.py +105 -0
- openocd/errors.py +43 -0
- openocd/events.py +112 -0
- openocd/flash.py +381 -0
- openocd/jtag/__init__.py +5 -0
- openocd/jtag/boundary.py +52 -0
- openocd/jtag/chain.py +218 -0
- openocd/jtag/scan.py +58 -0
- openocd/jtag/state.py +26 -0
- openocd/memory.py +243 -0
- openocd/process.py +148 -0
- openocd/py.typed +0 -0
- openocd/registers.py +186 -0
- openocd/rtt.py +233 -0
- openocd/session.py +330 -0
- openocd/svd/__init__.py +5 -0
- openocd/svd/decoder.py +54 -0
- openocd/svd/parser.py +128 -0
- openocd/svd/peripheral.py +186 -0
- openocd/target.py +164 -0
- openocd/transport.py +149 -0
- openocd/types.py +185 -0
- openocd_python-2026.2.12.dist-info/METADATA +75 -0
- openocd_python-2026.2.12.dist-info/RECORD +33 -0
- openocd_python-2026.2.12.dist-info/WHEEL +4 -0
- openocd_python-2026.2.12.dist-info/entry_points.txt +2 -0
- openocd_python-2026.2.12.dist-info/licenses/LICENSE +21 -0
openocd/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""openocd-python — typed, async-first Python bindings for OpenOCD."""
|
|
2
|
+
|
|
3
|
+
from openocd.errors import (
|
|
4
|
+
ConnectionError,
|
|
5
|
+
FlashError,
|
|
6
|
+
JTAGError,
|
|
7
|
+
OpenOCDError,
|
|
8
|
+
ProcessError,
|
|
9
|
+
SVDError,
|
|
10
|
+
TargetError,
|
|
11
|
+
TargetNotHaltedError,
|
|
12
|
+
TimeoutError,
|
|
13
|
+
)
|
|
14
|
+
from openocd.session import Session, SyncSession
|
|
15
|
+
from openocd.types import (
|
|
16
|
+
BitField,
|
|
17
|
+
Breakpoint,
|
|
18
|
+
DecodedRegister,
|
|
19
|
+
FlashBank,
|
|
20
|
+
FlashSector,
|
|
21
|
+
JTAGState,
|
|
22
|
+
MemoryRegion,
|
|
23
|
+
Register,
|
|
24
|
+
RTTChannel,
|
|
25
|
+
TAPInfo,
|
|
26
|
+
TargetState,
|
|
27
|
+
Watchpoint,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Session
|
|
32
|
+
"Session",
|
|
33
|
+
"SyncSession",
|
|
34
|
+
# Types
|
|
35
|
+
"BitField",
|
|
36
|
+
"Breakpoint",
|
|
37
|
+
"DecodedRegister",
|
|
38
|
+
"FlashBank",
|
|
39
|
+
"FlashSector",
|
|
40
|
+
"JTAGState",
|
|
41
|
+
"MemoryRegion",
|
|
42
|
+
"RTTChannel",
|
|
43
|
+
"Register",
|
|
44
|
+
"TAPInfo",
|
|
45
|
+
"TargetState",
|
|
46
|
+
"Watchpoint",
|
|
47
|
+
# Errors
|
|
48
|
+
"ConnectionError",
|
|
49
|
+
"FlashError",
|
|
50
|
+
"JTAGError",
|
|
51
|
+
"OpenOCDError",
|
|
52
|
+
"ProcessError",
|
|
53
|
+
"SVDError",
|
|
54
|
+
"TargetError",
|
|
55
|
+
"TargetNotHaltedError",
|
|
56
|
+
"TimeoutError",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
from importlib.metadata import version
|
|
61
|
+
|
|
62
|
+
__version__ = version("openocd-python")
|
|
63
|
+
except Exception:
|
|
64
|
+
__version__ = "0.0.0"
|
openocd/breakpoints.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Breakpoint and watchpoint management.
|
|
2
|
+
|
|
3
|
+
Wraps OpenOCD's ``bp``, ``rbp``, ``wp``, and ``rwp`` commands for
|
|
4
|
+
setting, removing, and listing hardware/software breakpoints and
|
|
5
|
+
data watchpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
from openocd.connection.base import Connection
|
|
16
|
+
from openocd.errors import OpenOCDError
|
|
17
|
+
from openocd.types import Breakpoint, Watchpoint
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BreakpointError(OpenOCDError):
|
|
23
|
+
"""A breakpoint or watchpoint operation failed."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Parsers
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
# Breakpoint(IVA): 0x08001234, 0x2, 1 (hw=1) or 0 (sw)
|
|
31
|
+
_BP_RE = re.compile(
|
|
32
|
+
r"Breakpoint\([^)]*\):\s*(?P<addr>0x[0-9a-fA-F]+),\s*"
|
|
33
|
+
r"(?P<len>0x[0-9a-fA-F]+),\s*(?P<hw>\d+)"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Watchpoint output varies across OpenOCD versions. Common formats:
|
|
37
|
+
# address: 0x20000000, len: 0x4, r/w/a: 2 (access), value: ...
|
|
38
|
+
# Watchpoint(DWT): 0x20000000, 0x4, 2
|
|
39
|
+
_WP_RE = re.compile(
|
|
40
|
+
r"(?:address:\s*(?P<addr1>0x[0-9a-fA-F]+).*?len:\s*(?P<len1>0x[0-9a-fA-F]+).*?r/w/a:\s*(?P<rwa1>\d+))"
|
|
41
|
+
r"|"
|
|
42
|
+
r"(?:Watchpoint\([^)]*\):\s*(?P<addr2>0x[0-9a-fA-F]+),\s*(?P<len2>0x[0-9a-fA-F]+),\s*(?P<rwa2>\d+))"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# OpenOCD watchpoint access type encoding
|
|
46
|
+
_WP_ACCESS_MAP = {0: "r", 1: "w", 2: "rw"}
|
|
47
|
+
_WP_ACCESS_CMD = {"r": "r", "w": "w", "rw": "a"}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _check_error(response: str, context: str) -> None:
|
|
51
|
+
"""Raise BreakpointError if the response indicates failure."""
|
|
52
|
+
if "error" in response.lower():
|
|
53
|
+
raise BreakpointError(f"{context}: {response.strip()}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_breakpoint_list(text: str) -> list[Breakpoint]:
|
|
57
|
+
"""Parse the output of ``bp`` (no arguments) into Breakpoint objects."""
|
|
58
|
+
breakpoints: list[Breakpoint] = []
|
|
59
|
+
for idx, m in enumerate(_BP_RE.finditer(text)):
|
|
60
|
+
hw_flag = int(m.group("hw"))
|
|
61
|
+
breakpoints.append(
|
|
62
|
+
Breakpoint(
|
|
63
|
+
number=idx,
|
|
64
|
+
type="hw" if hw_flag else "sw",
|
|
65
|
+
address=int(m.group("addr"), 16),
|
|
66
|
+
length=int(m.group("len"), 16),
|
|
67
|
+
enabled=True,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return breakpoints
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _parse_watchpoint_list(text: str) -> list[Watchpoint]:
|
|
74
|
+
"""Parse watchpoint listing output."""
|
|
75
|
+
watchpoints: list[Watchpoint] = []
|
|
76
|
+
for idx, m in enumerate(_WP_RE.finditer(text)):
|
|
77
|
+
# Match could come from either alternative in the regex
|
|
78
|
+
if m.group("addr1") is not None:
|
|
79
|
+
addr = int(m.group("addr1"), 16)
|
|
80
|
+
length = int(m.group("len1"), 16)
|
|
81
|
+
rwa = int(m.group("rwa1"))
|
|
82
|
+
else:
|
|
83
|
+
addr = int(m.group("addr2"), 16)
|
|
84
|
+
length = int(m.group("len2"), 16)
|
|
85
|
+
rwa = int(m.group("rwa2"))
|
|
86
|
+
|
|
87
|
+
watchpoints.append(
|
|
88
|
+
Watchpoint(
|
|
89
|
+
number=idx,
|
|
90
|
+
address=addr,
|
|
91
|
+
length=length,
|
|
92
|
+
access=_WP_ACCESS_MAP.get(rwa, "rw"),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
return watchpoints
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class BreakpointManager:
|
|
99
|
+
"""Manage breakpoints and watchpoints via OpenOCD.
|
|
100
|
+
|
|
101
|
+
Breakpoints can be either software (patching the instruction) or
|
|
102
|
+
hardware (using on-chip comparators). Watchpoints trigger on data
|
|
103
|
+
access to a given address range.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, conn: Connection) -> None:
|
|
107
|
+
self._conn = conn
|
|
108
|
+
|
|
109
|
+
# ------------------------------------------------------------------
|
|
110
|
+
# Breakpoints
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
async def add(self, address: int, length: int = 2, hw: bool = False) -> None:
|
|
114
|
+
"""Set a breakpoint at the given address.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
address: Instruction address for the breakpoint.
|
|
118
|
+
length: Breakpoint length in bytes (2 for Thumb, 4 for ARM).
|
|
119
|
+
hw: Request a hardware breakpoint. If False, OpenOCD uses a
|
|
120
|
+
software breakpoint when possible.
|
|
121
|
+
"""
|
|
122
|
+
cmd = f"bp 0x{address:08X} {length}"
|
|
123
|
+
if hw:
|
|
124
|
+
cmd += " hw"
|
|
125
|
+
resp = await self._conn.send(cmd)
|
|
126
|
+
_check_error(resp, f"bp 0x{address:08X}")
|
|
127
|
+
log.info("Breakpoint set at 0x%08X (len=%d, hw=%s)", address, length, hw)
|
|
128
|
+
|
|
129
|
+
async def remove(self, address: int) -> None:
|
|
130
|
+
"""Remove a breakpoint at the given address.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
address: Address of the breakpoint to remove.
|
|
134
|
+
"""
|
|
135
|
+
cmd = f"rbp 0x{address:08X}"
|
|
136
|
+
resp = await self._conn.send(cmd)
|
|
137
|
+
_check_error(resp, f"rbp 0x{address:08X}")
|
|
138
|
+
log.info("Breakpoint removed at 0x%08X", address)
|
|
139
|
+
|
|
140
|
+
async def list(self) -> list[Breakpoint]:
|
|
141
|
+
"""List all active breakpoints.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
A list of Breakpoint objects describing each active breakpoint.
|
|
145
|
+
"""
|
|
146
|
+
resp = await self._conn.send("bp")
|
|
147
|
+
# An empty response or no matches means no breakpoints set
|
|
148
|
+
if not resp.strip():
|
|
149
|
+
return []
|
|
150
|
+
return _parse_breakpoint_list(resp)
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------
|
|
153
|
+
# Watchpoints
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
async def add_watchpoint(
|
|
157
|
+
self,
|
|
158
|
+
address: int,
|
|
159
|
+
length: int,
|
|
160
|
+
access: Literal["r", "w", "rw"] = "rw",
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Set a data watchpoint.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
address: Memory address to watch.
|
|
166
|
+
length: Number of bytes to watch (must be power of 2 on most targets).
|
|
167
|
+
access: Access type -- ``"r"`` for read, ``"w"`` for write,
|
|
168
|
+
``"rw"`` for read/write (access).
|
|
169
|
+
"""
|
|
170
|
+
access_flag = _WP_ACCESS_CMD.get(access, "a")
|
|
171
|
+
cmd = f"wp 0x{address:08X} {length} {access_flag}"
|
|
172
|
+
resp = await self._conn.send(cmd)
|
|
173
|
+
_check_error(resp, f"wp 0x{address:08X}")
|
|
174
|
+
log.info("Watchpoint set at 0x%08X (len=%d, access=%s)", address, length, access)
|
|
175
|
+
|
|
176
|
+
async def remove_watchpoint(self, address: int) -> None:
|
|
177
|
+
"""Remove a watchpoint at the given address.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
address: Address of the watchpoint to remove.
|
|
181
|
+
"""
|
|
182
|
+
cmd = f"rwp 0x{address:08X}"
|
|
183
|
+
resp = await self._conn.send(cmd)
|
|
184
|
+
_check_error(resp, f"rwp 0x{address:08X}")
|
|
185
|
+
log.info("Watchpoint removed at 0x%08X", address)
|
|
186
|
+
|
|
187
|
+
async def list_watchpoints(self) -> list[Watchpoint]:
|
|
188
|
+
"""List all active watchpoints.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
A list of Watchpoint objects describing each active watchpoint.
|
|
192
|
+
"""
|
|
193
|
+
# OpenOCD doesn't have a dedicated "list watchpoints" command
|
|
194
|
+
# but 'wp' with no arguments on some builds returns the list.
|
|
195
|
+
# The more reliable approach is using the TCL command.
|
|
196
|
+
resp = await self._conn.send("wp")
|
|
197
|
+
if not resp.strip():
|
|
198
|
+
return []
|
|
199
|
+
return _parse_watchpoint_list(resp)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ======================================================================
|
|
203
|
+
# Sync wrapper
|
|
204
|
+
# ======================================================================
|
|
205
|
+
|
|
206
|
+
class SyncBreakpointManager:
|
|
207
|
+
"""Synchronous wrapper around BreakpointManager."""
|
|
208
|
+
|
|
209
|
+
def __init__(self, bp_manager: BreakpointManager, loop: asyncio.AbstractEventLoop) -> None:
|
|
210
|
+
self._bp = bp_manager
|
|
211
|
+
self._loop = loop
|
|
212
|
+
|
|
213
|
+
def add(self, address: int, length: int = 2, hw: bool = False) -> None:
|
|
214
|
+
self._loop.run_until_complete(self._bp.add(address, length=length, hw=hw))
|
|
215
|
+
|
|
216
|
+
def remove(self, address: int) -> None:
|
|
217
|
+
self._loop.run_until_complete(self._bp.remove(address))
|
|
218
|
+
|
|
219
|
+
def list(self) -> list[Breakpoint]:
|
|
220
|
+
return self._loop.run_until_complete(self._bp.list())
|
|
221
|
+
|
|
222
|
+
def add_watchpoint(
|
|
223
|
+
self,
|
|
224
|
+
address: int,
|
|
225
|
+
length: int,
|
|
226
|
+
access: Literal["r", "w", "rw"] = "rw",
|
|
227
|
+
) -> None:
|
|
228
|
+
self._loop.run_until_complete(self._bp.add_watchpoint(address, length, access=access))
|
|
229
|
+
|
|
230
|
+
def remove_watchpoint(self, address: int) -> None:
|
|
231
|
+
self._loop.run_until_complete(self._bp.remove_watchpoint(address))
|
|
232
|
+
|
|
233
|
+
def list_watchpoints(self) -> list[Watchpoint]:
|
|
234
|
+
return self._loop.run_until_complete(self._bp.list_watchpoints())
|
openocd/cli.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""CLI entry point for openocd-python.
|
|
2
|
+
|
|
3
|
+
Provides quick diagnostics and a REPL for interactive use:
|
|
4
|
+
|
|
5
|
+
$ openocd-python --help
|
|
6
|
+
$ openocd-python info # probe detection + target info
|
|
7
|
+
$ openocd-python repl # interactive command REPL
|
|
8
|
+
$ openocd-python read 0x08000000 16 # quick memory read
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import asyncio
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
try:
|
|
20
|
+
from importlib.metadata import version
|
|
21
|
+
|
|
22
|
+
pkg_version = version("openocd-python")
|
|
23
|
+
except Exception:
|
|
24
|
+
pkg_version = "dev"
|
|
25
|
+
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
prog="openocd-python",
|
|
28
|
+
description=f"OpenOCD Python bindings v{pkg_version}",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--version", action="version", version=f"openocd-python {pkg_version}"
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--host", default="localhost", help="OpenOCD host (default: localhost)"
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--port", type=int, default=6666, help="OpenOCD TCL RPC port (default: 6666)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
sub = parser.add_subparsers(dest="command")
|
|
41
|
+
|
|
42
|
+
sub.add_parser("info", help="Show target and adapter information")
|
|
43
|
+
|
|
44
|
+
repl_parser = sub.add_parser("repl", help="Interactive OpenOCD command REPL")
|
|
45
|
+
repl_parser.add_argument(
|
|
46
|
+
"--timeout", type=float, default=10.0, help="Command timeout in seconds"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
read_parser = sub.add_parser("read", help="Read memory and display as hexdump")
|
|
50
|
+
read_parser.add_argument("address", help="Start address (hex, e.g. 0x08000000)")
|
|
51
|
+
read_parser.add_argument(
|
|
52
|
+
"size", type=int, nargs="?", default=64, help="Bytes to read (default: 64)"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
sub.add_parser("scan", help="Scan the JTAG chain")
|
|
56
|
+
|
|
57
|
+
args = parser.parse_args()
|
|
58
|
+
|
|
59
|
+
if args.command is None:
|
|
60
|
+
parser.print_help()
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
asyncio.run(_dispatch(args))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def _dispatch(args: argparse.Namespace) -> None:
|
|
67
|
+
from openocd.session import Session
|
|
68
|
+
|
|
69
|
+
async with Session.connect(host=args.host, port=args.port) as ocd:
|
|
70
|
+
if args.command == "info":
|
|
71
|
+
await _cmd_info(ocd)
|
|
72
|
+
elif args.command == "repl":
|
|
73
|
+
await _cmd_repl(ocd, timeout=args.timeout)
|
|
74
|
+
elif args.command == "read":
|
|
75
|
+
await _cmd_read(ocd, args.address, args.size)
|
|
76
|
+
elif args.command == "scan":
|
|
77
|
+
await _cmd_scan(ocd)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def _cmd_info(ocd) -> None:
|
|
81
|
+
"""Display target state and adapter information."""
|
|
82
|
+
from openocd.errors import OpenOCDError
|
|
83
|
+
|
|
84
|
+
print("=== OpenOCD Target Info ===\n")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
state = await ocd.target.state()
|
|
88
|
+
print(f" Target: {state.name}")
|
|
89
|
+
print(f" State: {state.state}")
|
|
90
|
+
if state.current_pc is not None:
|
|
91
|
+
print(f" PC: 0x{state.current_pc:08X}")
|
|
92
|
+
except OpenOCDError as e:
|
|
93
|
+
print(f" Target: (error: {e})")
|
|
94
|
+
|
|
95
|
+
print()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
transport_name = await ocd.transport.select()
|
|
99
|
+
print(f" Transport: {transport_name}")
|
|
100
|
+
except OpenOCDError:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
adapter = await ocd.transport.adapter_info()
|
|
105
|
+
print(f" Adapter: {adapter}")
|
|
106
|
+
except OpenOCDError:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
speed = await ocd.transport.adapter_speed()
|
|
111
|
+
print(f" Speed: {speed} kHz")
|
|
112
|
+
except OpenOCDError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def _cmd_repl(ocd, timeout: float = 10.0) -> None:
|
|
117
|
+
"""Interactive command REPL."""
|
|
118
|
+
print("OpenOCD REPL (type 'quit' or Ctrl-D to exit)\n")
|
|
119
|
+
while True:
|
|
120
|
+
try:
|
|
121
|
+
cmd = input("ocd> ")
|
|
122
|
+
except (EOFError, KeyboardInterrupt):
|
|
123
|
+
print()
|
|
124
|
+
break
|
|
125
|
+
if cmd.strip().lower() in ("quit", "exit", "q"):
|
|
126
|
+
break
|
|
127
|
+
if not cmd.strip():
|
|
128
|
+
continue
|
|
129
|
+
try:
|
|
130
|
+
result = await ocd.command(cmd)
|
|
131
|
+
if result.strip():
|
|
132
|
+
print(result)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"Error: {e}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def _cmd_read(ocd, address_str: str, size: int) -> None:
|
|
138
|
+
"""Read memory and display as hexdump."""
|
|
139
|
+
addr = int(address_str, 0)
|
|
140
|
+
dump = await ocd.memory.hexdump(addr, size)
|
|
141
|
+
print(dump)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def _cmd_scan(ocd) -> None:
|
|
145
|
+
"""Scan the JTAG chain."""
|
|
146
|
+
taps = await ocd.jtag.scan_chain()
|
|
147
|
+
if not taps:
|
|
148
|
+
print("No TAPs found on the JTAG chain.")
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
print(f"{'TAP Name':<25s} {'IDCODE':>10s} IR Enabled")
|
|
152
|
+
print("-" * 50)
|
|
153
|
+
for tap in taps:
|
|
154
|
+
print(
|
|
155
|
+
f"{tap.name:<25s} 0x{tap.idcode:08X} {tap.ir_length:>2d} "
|
|
156
|
+
f"{'yes' if tap.enabled else 'no'}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
main()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Connection backends for communicating with OpenOCD."""
|
|
2
|
+
|
|
3
|
+
from openocd.connection.base import Connection
|
|
4
|
+
from openocd.connection.tcl_rpc import TclRpcConnection
|
|
5
|
+
from openocd.connection.telnet import TelnetConnection
|
|
6
|
+
|
|
7
|
+
__all__ = ["Connection", "TclRpcConnection", "TelnetConnection"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Abstract base class for OpenOCD connection backends."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Connection(ABC):
|
|
10
|
+
"""Protocol-agnostic interface to an OpenOCD instance."""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
async def connect(self, host: str, port: int) -> None:
|
|
14
|
+
"""Open a connection to the given host and port."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def send(self, command: str) -> str:
|
|
18
|
+
"""Send a command string and return the response."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
async def close(self) -> None:
|
|
22
|
+
"""Close the connection."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def enable_notifications(self) -> None:
|
|
26
|
+
"""Enable asynchronous event notifications from OpenOCD."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def on_notification(self, callback: Callable[[str], None]) -> None:
|
|
30
|
+
"""Register a callback for incoming notifications."""
|