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/rtt.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""Real-Time Transfer (RTT) support via OpenOCD.
|
|
2
|
+
|
|
3
|
+
SEGGER RTT provides high-speed bidirectional communication between
|
|
4
|
+
a debug host and an embedded target using shared memory in RAM.
|
|
5
|
+
OpenOCD exposes RTT through its ``rtt`` command family.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import contextlib
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from openocd.errors import OpenOCDError
|
|
16
|
+
from openocd.types import RTTChannel
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from openocd.connection.base import Connection
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RTTManager:
|
|
25
|
+
"""Control and use SEGGER RTT channels via OpenOCD.
|
|
26
|
+
|
|
27
|
+
Typical flow::
|
|
28
|
+
|
|
29
|
+
rtt = RTTManager(conn)
|
|
30
|
+
await rtt.setup(address=0x20000000, size=0x1000)
|
|
31
|
+
await rtt.start()
|
|
32
|
+
channels = await rtt.channels()
|
|
33
|
+
data = await rtt.read(0)
|
|
34
|
+
await rtt.write(0, "hello\\n")
|
|
35
|
+
await rtt.stop()
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, conn: Connection) -> None:
|
|
39
|
+
self._conn = conn
|
|
40
|
+
|
|
41
|
+
async def setup(
|
|
42
|
+
self,
|
|
43
|
+
address: int,
|
|
44
|
+
size: int,
|
|
45
|
+
id_string: str = "SEGGER RTT",
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Configure RTT control block search parameters.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
address: Start address of the RAM region to search.
|
|
51
|
+
size: Size of the search region in bytes.
|
|
52
|
+
id_string: RTT control block identifier (default "SEGGER RTT").
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
OpenOCDError: If the setup command fails.
|
|
56
|
+
"""
|
|
57
|
+
cmd = f'rtt setup 0x{address:X} 0x{size:X} "{id_string}"'
|
|
58
|
+
response = await self._conn.send(cmd)
|
|
59
|
+
_check_rtt_response(response, cmd)
|
|
60
|
+
log.info(
|
|
61
|
+
"RTT setup: search 0x%08X +0x%X id=%r",
|
|
62
|
+
address,
|
|
63
|
+
size,
|
|
64
|
+
id_string,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
async def start(self) -> None:
|
|
68
|
+
"""Start RTT — searches for the control block and activates channels.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
OpenOCDError: If the control block is not found or start fails.
|
|
72
|
+
"""
|
|
73
|
+
response = await self._conn.send("rtt start")
|
|
74
|
+
_check_rtt_response(response, "rtt start")
|
|
75
|
+
log.info("RTT started")
|
|
76
|
+
|
|
77
|
+
async def stop(self) -> None:
|
|
78
|
+
"""Stop RTT communication.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
OpenOCDError: If the stop command fails.
|
|
82
|
+
"""
|
|
83
|
+
response = await self._conn.send("rtt stop")
|
|
84
|
+
_check_rtt_response(response, "rtt stop")
|
|
85
|
+
log.info("RTT stopped")
|
|
86
|
+
|
|
87
|
+
async def channels(self) -> list[RTTChannel]:
|
|
88
|
+
"""List available RTT channels.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of RTTChannel descriptors (index, name, size, direction).
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
OpenOCDError: If the channels command fails.
|
|
95
|
+
"""
|
|
96
|
+
response = await self._conn.send("rtt channels")
|
|
97
|
+
_check_rtt_response(response, "rtt channels")
|
|
98
|
+
return _parse_channels(response)
|
|
99
|
+
|
|
100
|
+
async def read(self, channel: int) -> str:
|
|
101
|
+
"""Read pending data from an RTT up-channel.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
channel: Channel index (typically 0 for Terminal).
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The data read as a string (may be empty if nothing pending).
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
OpenOCDError: If the read command fails.
|
|
111
|
+
"""
|
|
112
|
+
cmd = f"rtt channelread {channel}"
|
|
113
|
+
response = await self._conn.send(cmd)
|
|
114
|
+
_check_rtt_response(response, cmd)
|
|
115
|
+
return response
|
|
116
|
+
|
|
117
|
+
async def write(self, channel: int, data: str) -> None:
|
|
118
|
+
"""Write data to an RTT down-channel.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
channel: Channel index (typically 0 for Terminal).
|
|
122
|
+
data: String data to send to the target.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
OpenOCDError: If the write command fails.
|
|
126
|
+
"""
|
|
127
|
+
# Escape TCL special characters to prevent injection
|
|
128
|
+
escaped = (
|
|
129
|
+
data.replace("\\", "\\\\")
|
|
130
|
+
.replace('"', '\\"')
|
|
131
|
+
.replace("[", "\\[")
|
|
132
|
+
.replace("$", "\\$")
|
|
133
|
+
)
|
|
134
|
+
cmd = f'rtt channelwrite {channel} "{escaped}"'
|
|
135
|
+
response = await self._conn.send(cmd)
|
|
136
|
+
_check_rtt_response(response, cmd)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SyncRTTManager:
|
|
140
|
+
"""Synchronous wrapper around RTTManager."""
|
|
141
|
+
|
|
142
|
+
def __init__(self, manager: RTTManager, loop: asyncio.AbstractEventLoop) -> None:
|
|
143
|
+
self._manager = manager
|
|
144
|
+
self._loop = loop
|
|
145
|
+
|
|
146
|
+
def setup(
|
|
147
|
+
self,
|
|
148
|
+
address: int,
|
|
149
|
+
size: int,
|
|
150
|
+
id_string: str = "SEGGER RTT",
|
|
151
|
+
) -> None:
|
|
152
|
+
self._loop.run_until_complete(
|
|
153
|
+
self._manager.setup(address, size, id_string)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def start(self) -> None:
|
|
157
|
+
self._loop.run_until_complete(self._manager.start())
|
|
158
|
+
|
|
159
|
+
def stop(self) -> None:
|
|
160
|
+
self._loop.run_until_complete(self._manager.stop())
|
|
161
|
+
|
|
162
|
+
def channels(self) -> list[RTTChannel]:
|
|
163
|
+
return self._loop.run_until_complete(self._manager.channels())
|
|
164
|
+
|
|
165
|
+
def read(self, channel: int) -> str:
|
|
166
|
+
return self._loop.run_until_complete(self._manager.read(channel))
|
|
167
|
+
|
|
168
|
+
def write(self, channel: int, data: str) -> None:
|
|
169
|
+
self._loop.run_until_complete(self._manager.write(channel, data))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
# Helpers
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
def _check_rtt_response(response: str, command: str) -> None:
|
|
177
|
+
"""Raise on error responses from RTT commands."""
|
|
178
|
+
if response and "error" in response.lower():
|
|
179
|
+
raise OpenOCDError(f"RTT command failed ({command}): {response}")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _parse_channels(response: str) -> list[RTTChannel]:
|
|
183
|
+
"""Parse the output of ``rtt channels`` into RTTChannel objects.
|
|
184
|
+
|
|
185
|
+
OpenOCD typically outputs lines like::
|
|
186
|
+
|
|
187
|
+
Up-channels:
|
|
188
|
+
0: Terminal 1024
|
|
189
|
+
Down-channels:
|
|
190
|
+
0: Terminal 16
|
|
191
|
+
|
|
192
|
+
The exact format may vary by OpenOCD version; this parser is
|
|
193
|
+
intentionally lenient.
|
|
194
|
+
"""
|
|
195
|
+
channels: list[RTTChannel] = []
|
|
196
|
+
direction = "up"
|
|
197
|
+
|
|
198
|
+
for line in response.splitlines():
|
|
199
|
+
stripped = line.strip()
|
|
200
|
+
lower = stripped.lower()
|
|
201
|
+
|
|
202
|
+
if "up-channel" in lower or lower.startswith("up"):
|
|
203
|
+
direction = "up"
|
|
204
|
+
continue
|
|
205
|
+
if "down-channel" in lower or lower.startswith("down"):
|
|
206
|
+
direction = "down"
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Try to parse lines like "0: Terminal 1024"
|
|
210
|
+
if ":" in stripped and stripped[0].isdigit():
|
|
211
|
+
parts = stripped.split(":", 1)
|
|
212
|
+
try:
|
|
213
|
+
index = int(parts[0].strip())
|
|
214
|
+
except ValueError:
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
rest = parts[1].strip().split()
|
|
218
|
+
name = rest[0] if rest else f"channel_{index}"
|
|
219
|
+
size = 0
|
|
220
|
+
if len(rest) >= 2:
|
|
221
|
+
with contextlib.suppress(ValueError):
|
|
222
|
+
size = int(rest[-1])
|
|
223
|
+
|
|
224
|
+
channels.append(
|
|
225
|
+
RTTChannel(
|
|
226
|
+
index=index,
|
|
227
|
+
name=name,
|
|
228
|
+
size=size,
|
|
229
|
+
direction=direction,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return channels
|
openocd/session.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Session — the main entry point for openocd-python.
|
|
2
|
+
|
|
3
|
+
Manages the connection lifecycle and provides access to all subsystems
|
|
4
|
+
(target, memory, registers, flash, JTAG, SVD, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from openocd.connection.tcl_rpc import TclRpcConnection
|
|
16
|
+
from openocd.process import OpenOCDProcess
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from openocd.breakpoints import BreakpointManager, SyncBreakpointManager
|
|
20
|
+
from openocd.flash import Flash, SyncFlash
|
|
21
|
+
from openocd.jtag import JTAGController, SyncJTAGController
|
|
22
|
+
from openocd.memory import Memory, SyncMemory
|
|
23
|
+
from openocd.registers import Registers, SyncRegisters
|
|
24
|
+
from openocd.rtt import RTTManager
|
|
25
|
+
from openocd.svd import SVDManager, SyncSVDManager
|
|
26
|
+
from openocd.target import SyncTarget, Target
|
|
27
|
+
from openocd.transport import Transport
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Session:
|
|
33
|
+
"""Main entry point. Manages connection and provides access to subsystems."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, connection: TclRpcConnection, process: OpenOCDProcess | None = None) -> None:
|
|
36
|
+
self._conn = connection
|
|
37
|
+
self._process = process
|
|
38
|
+
self._target: Target | None = None
|
|
39
|
+
self._memory: Memory | None = None
|
|
40
|
+
self._registers: Registers | None = None
|
|
41
|
+
self._flash: Flash | None = None
|
|
42
|
+
self._jtag: JTAGController | None = None
|
|
43
|
+
self._breakpoints: BreakpointManager | None = None
|
|
44
|
+
self._rtt: RTTManager | None = None
|
|
45
|
+
self._svd: SVDManager | None = None
|
|
46
|
+
self._transport: Transport | None = None
|
|
47
|
+
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
# Factory methods
|
|
50
|
+
# ------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
async def start(
|
|
54
|
+
cls,
|
|
55
|
+
config: str | Path,
|
|
56
|
+
*,
|
|
57
|
+
tcl_port: int = 6666,
|
|
58
|
+
openocd_bin: str | None = None,
|
|
59
|
+
timeout: float = 10.0,
|
|
60
|
+
extra_args: list[str] | None = None,
|
|
61
|
+
) -> Session:
|
|
62
|
+
"""Spawn an OpenOCD process and connect to it.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
config: Config file path or ``-f``/``-c`` flags string.
|
|
66
|
+
tcl_port: TCL RPC port.
|
|
67
|
+
openocd_bin: Custom OpenOCD binary path.
|
|
68
|
+
timeout: Seconds to wait for OpenOCD readiness.
|
|
69
|
+
extra_args: Additional CLI arguments for OpenOCD.
|
|
70
|
+
"""
|
|
71
|
+
proc = OpenOCDProcess()
|
|
72
|
+
await proc.start(
|
|
73
|
+
str(config), extra_args=extra_args, tcl_port=tcl_port, openocd_bin=openocd_bin
|
|
74
|
+
)
|
|
75
|
+
await proc.wait_ready(timeout=timeout)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
conn = TclRpcConnection(timeout=timeout)
|
|
79
|
+
await conn.connect("localhost", tcl_port)
|
|
80
|
+
except Exception:
|
|
81
|
+
await proc.stop()
|
|
82
|
+
raise
|
|
83
|
+
|
|
84
|
+
return cls(connection=conn, process=proc)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
async def connect(
|
|
88
|
+
cls,
|
|
89
|
+
host: str = "localhost",
|
|
90
|
+
port: int = 6666,
|
|
91
|
+
timeout: float = 10.0,
|
|
92
|
+
) -> Session:
|
|
93
|
+
"""Connect to an already-running OpenOCD instance."""
|
|
94
|
+
conn = TclRpcConnection(timeout=timeout)
|
|
95
|
+
await conn.connect(host, port)
|
|
96
|
+
return cls(connection=conn)
|
|
97
|
+
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
# Sync factory wrappers
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def start_sync(cls, config: str | Path, **kwargs) -> SyncSession:
|
|
104
|
+
"""Synchronous version of start(). Returns a SyncSession."""
|
|
105
|
+
loop = _get_or_create_loop()
|
|
106
|
+
session = loop.run_until_complete(cls.start(config, **kwargs))
|
|
107
|
+
return SyncSession(session, loop)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def connect_sync(cls, host: str = "localhost", port: int = 6666, **kwargs) -> SyncSession:
|
|
111
|
+
"""Synchronous version of connect(). Returns a SyncSession."""
|
|
112
|
+
loop = _get_or_create_loop()
|
|
113
|
+
session = loop.run_until_complete(cls.connect(host, port, **kwargs))
|
|
114
|
+
return SyncSession(session, loop)
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
# Context manager
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
async def __aenter__(self) -> Session:
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
async def __aexit__(self, *exc) -> None:
|
|
124
|
+
await self.close()
|
|
125
|
+
|
|
126
|
+
async def close(self) -> None:
|
|
127
|
+
"""Close the connection and stop the subprocess if we spawned it."""
|
|
128
|
+
await self._conn.close()
|
|
129
|
+
if self._process:
|
|
130
|
+
await self._process.stop()
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# Raw command escape hatch
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
async def command(self, cmd: str) -> str:
|
|
137
|
+
"""Send a raw OpenOCD command and return the response string."""
|
|
138
|
+
return await self._conn.send(cmd)
|
|
139
|
+
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
# Subsystem accessors (lazy-initialized)
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def target(self) -> Target:
|
|
146
|
+
if self._target is None:
|
|
147
|
+
from openocd.target import Target
|
|
148
|
+
self._target = Target(self._conn)
|
|
149
|
+
return self._target
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def memory(self) -> Memory:
|
|
153
|
+
if self._memory is None:
|
|
154
|
+
from openocd.memory import Memory
|
|
155
|
+
self._memory = Memory(self._conn)
|
|
156
|
+
return self._memory
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def registers(self) -> Registers:
|
|
160
|
+
if self._registers is None:
|
|
161
|
+
from openocd.registers import Registers
|
|
162
|
+
self._registers = Registers(self._conn)
|
|
163
|
+
return self._registers
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def flash(self) -> Flash:
|
|
167
|
+
if self._flash is None:
|
|
168
|
+
from openocd.flash import Flash
|
|
169
|
+
self._flash = Flash(self._conn)
|
|
170
|
+
return self._flash
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def jtag(self) -> JTAGController:
|
|
174
|
+
if self._jtag is None:
|
|
175
|
+
from openocd.jtag import JTAGController
|
|
176
|
+
self._jtag = JTAGController(self._conn)
|
|
177
|
+
return self._jtag
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def breakpoints(self) -> BreakpointManager:
|
|
181
|
+
if self._breakpoints is None:
|
|
182
|
+
from openocd.breakpoints import BreakpointManager
|
|
183
|
+
self._breakpoints = BreakpointManager(self._conn)
|
|
184
|
+
return self._breakpoints
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def rtt(self) -> RTTManager:
|
|
188
|
+
if self._rtt is None:
|
|
189
|
+
from openocd.rtt import RTTManager
|
|
190
|
+
self._rtt = RTTManager(self._conn)
|
|
191
|
+
return self._rtt
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def svd(self) -> SVDManager:
|
|
195
|
+
if self._svd is None:
|
|
196
|
+
from openocd.svd import SVDManager
|
|
197
|
+
self._svd = SVDManager(self._conn, self.memory)
|
|
198
|
+
return self._svd
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def transport(self) -> Transport:
|
|
202
|
+
if self._transport is None:
|
|
203
|
+
from openocd.transport import Transport
|
|
204
|
+
self._transport = Transport(self._conn)
|
|
205
|
+
return self._transport
|
|
206
|
+
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
# Event shortcuts
|
|
209
|
+
# ------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
def on_halt(self, callback: Callable[[str], None]) -> None:
|
|
212
|
+
"""Register a callback for target halt events."""
|
|
213
|
+
def _filter(msg: str) -> None:
|
|
214
|
+
if "halted" in msg.lower():
|
|
215
|
+
callback(msg)
|
|
216
|
+
self._conn.on_notification(_filter)
|
|
217
|
+
|
|
218
|
+
def on_reset(self, callback: Callable[[str], None]) -> None:
|
|
219
|
+
"""Register a callback for target reset events."""
|
|
220
|
+
def _filter(msg: str) -> None:
|
|
221
|
+
if "reset" in msg.lower():
|
|
222
|
+
callback(msg)
|
|
223
|
+
self._conn.on_notification(_filter)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ======================================================================
|
|
227
|
+
# Sync wrapper
|
|
228
|
+
# ======================================================================
|
|
229
|
+
|
|
230
|
+
class SyncSession:
|
|
231
|
+
"""Wraps an async Session for synchronous use."""
|
|
232
|
+
|
|
233
|
+
def __init__(self, session: Session, loop: asyncio.AbstractEventLoop) -> None:
|
|
234
|
+
self._session = session
|
|
235
|
+
self._loop = loop
|
|
236
|
+
self._target: SyncTarget | None = None
|
|
237
|
+
self._memory: SyncMemory | None = None
|
|
238
|
+
self._registers: SyncRegisters | None = None
|
|
239
|
+
self._flash: SyncFlash | None = None
|
|
240
|
+
self._jtag: SyncJTAGController | None = None
|
|
241
|
+
self._breakpoints: SyncBreakpointManager | None = None
|
|
242
|
+
self._svd: SyncSVDManager | None = None
|
|
243
|
+
|
|
244
|
+
def __enter__(self) -> SyncSession:
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
def __exit__(self, *exc) -> None:
|
|
248
|
+
self._loop.run_until_complete(self._session.close())
|
|
249
|
+
|
|
250
|
+
def command(self, cmd: str) -> str:
|
|
251
|
+
return self._loop.run_until_complete(self._session.command(cmd))
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def target(self) -> SyncTarget:
|
|
255
|
+
if self._target is None:
|
|
256
|
+
from openocd.target import SyncTarget
|
|
257
|
+
self._target = SyncTarget(self._session.target, self._loop)
|
|
258
|
+
return self._target
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def memory(self) -> SyncMemory:
|
|
262
|
+
if self._memory is None:
|
|
263
|
+
from openocd.memory import SyncMemory
|
|
264
|
+
self._memory = SyncMemory(self._session.memory, self._loop)
|
|
265
|
+
return self._memory
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def registers(self) -> SyncRegisters:
|
|
269
|
+
if self._registers is None:
|
|
270
|
+
from openocd.registers import SyncRegisters
|
|
271
|
+
self._registers = SyncRegisters(self._session.registers, self._loop)
|
|
272
|
+
return self._registers
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def flash(self) -> SyncFlash:
|
|
276
|
+
if self._flash is None:
|
|
277
|
+
from openocd.flash import SyncFlash
|
|
278
|
+
self._flash = SyncFlash(self._session.flash, self._loop)
|
|
279
|
+
return self._flash
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def jtag(self) -> SyncJTAGController:
|
|
283
|
+
if self._jtag is None:
|
|
284
|
+
from openocd.jtag import SyncJTAGController
|
|
285
|
+
self._jtag = SyncJTAGController(self._session.jtag, self._loop)
|
|
286
|
+
return self._jtag
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def breakpoints(self) -> SyncBreakpointManager:
|
|
290
|
+
if self._breakpoints is None:
|
|
291
|
+
from openocd.breakpoints import SyncBreakpointManager
|
|
292
|
+
self._breakpoints = SyncBreakpointManager(self._session.breakpoints, self._loop)
|
|
293
|
+
return self._breakpoints
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def svd(self) -> SyncSVDManager:
|
|
297
|
+
if self._svd is None:
|
|
298
|
+
from openocd.svd import SyncSVDManager
|
|
299
|
+
self._svd = SyncSVDManager(self._session.svd, self._loop)
|
|
300
|
+
return self._svd
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ======================================================================
|
|
304
|
+
# Helpers
|
|
305
|
+
# ======================================================================
|
|
306
|
+
|
|
307
|
+
def _get_or_create_loop() -> asyncio.AbstractEventLoop:
|
|
308
|
+
"""Get or create an event loop for synchronous usage.
|
|
309
|
+
|
|
310
|
+
Raises RuntimeError if called from within an already-running async
|
|
311
|
+
context (where ``run_until_complete`` would deadlock).
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
asyncio.get_running_loop()
|
|
315
|
+
except RuntimeError:
|
|
316
|
+
pass # No running loop — this is the expected path for sync usage
|
|
317
|
+
else:
|
|
318
|
+
raise RuntimeError(
|
|
319
|
+
"Cannot use sync API from an async context. "
|
|
320
|
+
"Use the async Session.start()/connect() instead."
|
|
321
|
+
)
|
|
322
|
+
try:
|
|
323
|
+
loop = asyncio.get_event_loop()
|
|
324
|
+
if loop.is_closed():
|
|
325
|
+
loop = asyncio.new_event_loop()
|
|
326
|
+
asyncio.set_event_loop(loop)
|
|
327
|
+
except RuntimeError:
|
|
328
|
+
loop = asyncio.new_event_loop()
|
|
329
|
+
asyncio.set_event_loop(loop)
|
|
330
|
+
return loop
|
openocd/svd/__init__.py
ADDED
openocd/svd/decoder.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Register value decoding using SVD metadata.
|
|
2
|
+
|
|
3
|
+
Takes a raw integer read from hardware and splits it into named bitfields
|
|
4
|
+
using the field definitions from a parsed SVD file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from openocd.types import BitField, DecodedRegister
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def decode_register(
|
|
15
|
+
peripheral_obj: Any,
|
|
16
|
+
register_obj: Any,
|
|
17
|
+
raw_value: int,
|
|
18
|
+
) -> DecodedRegister:
|
|
19
|
+
"""Decode a raw register value into named bitfields using SVD metadata.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
peripheral_obj: cmsis_svd peripheral (used for base_address and name).
|
|
23
|
+
register_obj: cmsis_svd register (used for fields, address_offset, name).
|
|
24
|
+
raw_value: The 32-bit value read from hardware.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A DecodedRegister with all fields extracted and annotated.
|
|
28
|
+
"""
|
|
29
|
+
address = peripheral_obj.base_address + register_obj.address_offset
|
|
30
|
+
fields: list[BitField] = []
|
|
31
|
+
|
|
32
|
+
for svd_field in register_obj.fields or []:
|
|
33
|
+
mask = ((1 << svd_field.bit_width) - 1) << svd_field.bit_offset
|
|
34
|
+
value = (raw_value & mask) >> svd_field.bit_offset
|
|
35
|
+
fields.append(
|
|
36
|
+
BitField(
|
|
37
|
+
name=svd_field.name,
|
|
38
|
+
offset=svd_field.bit_offset,
|
|
39
|
+
width=svd_field.bit_width,
|
|
40
|
+
value=value,
|
|
41
|
+
description=svd_field.description or "",
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Sort fields by bit offset (low to high) for consistent display
|
|
46
|
+
fields.sort(key=lambda f: f.offset)
|
|
47
|
+
|
|
48
|
+
return DecodedRegister(
|
|
49
|
+
peripheral=peripheral_obj.name,
|
|
50
|
+
register=register_obj.name,
|
|
51
|
+
address=address,
|
|
52
|
+
raw_value=raw_value,
|
|
53
|
+
fields=fields,
|
|
54
|
+
)
|