oep-client-python 0.0.20__py3-none-any.whl → 0.0.22__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.
- oep_client/__init__.py +1 -1
- oep_client/__main__.py +76 -28
- oep_client/arm.py +11 -9
- oep_client/capture.py +157 -49
- oep_client/catalog.py +16 -9
- oep_client/config.py +202 -63
- oep_client/console.py +68 -18
- oep_client/core.py +21 -3
- oep_client/endpoint.py +685 -255
- oep_client/fake.py +69 -51
- oep_client/fake_capture.py +107 -51
- oep_client/fake_serve.py +4 -4
- oep_client/fixture.py +56 -16
- oep_client/host.py +88 -29
- oep_client/interfaces.py +14 -8
- oep_client/link.py +5 -4
- oep_client/message.py +49 -17
- oep_client/registry.py +26 -21
- oep_client/riscv.py +143 -56
- oep_client/usb_stream.py +10 -4
- {oep_client_python-0.0.20.dist-info → oep_client_python-0.0.22.dist-info}/METADATA +17 -13
- oep_client_python-0.0.22.dist-info/RECORD +35 -0
- oep_client_python-0.0.20.dist-info/RECORD +0 -35
- {oep_client_python-0.0.20.dist-info → oep_client_python-0.0.22.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.20.dist-info → oep_client_python-0.0.22.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.20.dist-info → oep_client_python-0.0.22.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""The oep command: what a probe offers (dump) and its settings (config).
|
|
2
2
|
|
|
3
3
|
oep dump --port /run/board-identify/by-id/<probe> oep dump --fake p4-x035 --prefix oep.target --json
|
|
4
|
-
oep config show <probe>
|
|
4
|
+
oep config show <probe> (the settings and what the probe declares; lock-free)
|
|
5
|
+
oep config state <probe> (the live slot / bind / storage state; lock-free)
|
|
5
6
|
oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --retry 1 --mechanism dmseq
|
|
6
7
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
7
|
-
oep config plan <probe> oep.fixture.uart#
|
|
8
|
+
oep config plan <probe> oep.fixture.uart#1 rx=48 tx=49 (the fn's whole plan; fn number or name#instance)
|
|
9
|
+
oep config uart <probe> oep.fixture.uart#1 115200 --format 8N1
|
|
8
10
|
oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
|
|
9
11
|
|
|
10
12
|
<probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
|
|
@@ -52,9 +54,12 @@ def main(argv=None) -> int:
|
|
|
52
54
|
def _config_parser(sub) -> None:
|
|
53
55
|
c = sub.add_parser("config", help="the probe's settings (oep.probe.config): slots, binds, save")
|
|
54
56
|
cs = c.add_subparsers(dest="action", required=True)
|
|
55
|
-
show = cs.add_parser("show", help="the settings and the live slot / bind state")
|
|
57
|
+
show = cs.add_parser("show", help="the settings, what the probe declares, and the live slot / bind state")
|
|
56
58
|
show.add_argument("probe")
|
|
57
59
|
show.add_argument("--json", action="store_true")
|
|
60
|
+
state = cs.add_parser("state", help="the live slot / bind / storage state (op state, lock-free)")
|
|
61
|
+
state.add_argument("probe")
|
|
62
|
+
state.add_argument("--json", action="store_true")
|
|
58
63
|
slot = cs.add_parser("slot", help="register a slot (a place a target is wired to)")
|
|
59
64
|
slot.add_argument("probe")
|
|
60
65
|
slot.add_argument("--slot", type=int, default=0, help="the slot number (default 0)")
|
|
@@ -62,8 +67,9 @@ def _config_parser(sub) -> None:
|
|
|
62
67
|
slot.add_argument("--wire", help="rvswd or swio (or an fn); default: the probe's only wire")
|
|
63
68
|
slot.add_argument("--pins", help="swdio,swclk (one pin on swio); default: the wire's only pin set")
|
|
64
69
|
slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
|
|
65
|
-
slot.add_argument("--retry", type=
|
|
66
|
-
slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq"
|
|
70
|
+
slot.add_argument("--retry", type=float, default=0, help="at-boot: try again every N s while absent (0: never)")
|
|
71
|
+
slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq",
|
|
72
|
+
help="the console's mechanism, or none (no console on this slot)")
|
|
67
73
|
slot.add_argument("--max-speed", type=int, default=0,
|
|
68
74
|
help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
|
|
69
75
|
slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
|
|
@@ -79,7 +85,7 @@ def _config_parser(sub) -> None:
|
|
|
79
85
|
bind.add_argument("--save", action="store_true")
|
|
80
86
|
plan = cs.add_parser("plan", help="the pins an interface keeps (its whole plan, kept as a setting)")
|
|
81
87
|
plan.add_argument("probe")
|
|
82
|
-
plan.add_argument("fn", help="an fn, or
|
|
88
|
+
plan.add_argument("fn", help="an fn, or name#instance (the list instance, 0 = the first; #0 may be left out, core §7.2)")
|
|
83
89
|
plan.add_argument("roles", nargs="+", help="ROLE=CHANNEL, ROLE a number or the interface's role name (rx, tx, line...)")
|
|
84
90
|
plan.add_argument("--save", action="store_true")
|
|
85
91
|
label = cs.add_parser("label", help="name a channel (shown in oep.core's describe)")
|
|
@@ -92,9 +98,15 @@ def _config_parser(sub) -> None:
|
|
|
92
98
|
idle.add_argument("channel", type=int)
|
|
93
99
|
idle.add_argument("mode", choices=sorted(config.IDLE))
|
|
94
100
|
idle.add_argument("--save", action="store_true")
|
|
95
|
-
|
|
101
|
+
uart = cs.add_parser("uart", help="a fixture UART's baud / format, applied whenever its plan has RX or TX")
|
|
102
|
+
uart.add_argument("probe")
|
|
103
|
+
uart.add_argument("fn", help="an fn, or name#instance of the oep.fixture.uart")
|
|
104
|
+
uart.add_argument("baud", type=int)
|
|
105
|
+
uart.add_argument("--format", default="8N1", help="data bits, parity, stop bits: 8N1 (default), 8E1, 7O2 ...")
|
|
106
|
+
uart.add_argument("--save", action="store_true")
|
|
107
|
+
rm = cs.add_parser("remove", help="remove one item (unset): slot N, bind PORT, plan FN, label CH, idle CH, uart FN")
|
|
96
108
|
rm.add_argument("probe")
|
|
97
|
-
rm.add_argument("kind", choices=
|
|
109
|
+
rm.add_argument("kind", choices=sorted(config.ITEM))
|
|
98
110
|
rm.add_argument("key", type=int)
|
|
99
111
|
rm.add_argument("--save", action="store_true")
|
|
100
112
|
for name in ("save", "erase"):
|
|
@@ -108,7 +120,7 @@ def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
|
|
|
108
120
|
groups = []
|
|
109
121
|
for tag, v in core.describe(hst, fn):
|
|
110
122
|
if tag & 0x7F == catalog.CHANNEL_GROUP:
|
|
111
|
-
roles =
|
|
123
|
+
roles = dict(catalog.unpack_channel_group(v)[1])
|
|
112
124
|
groups.append((roles.get(1, 0xFFFF), roles.get(2, 0xFFFF)))
|
|
113
125
|
if len(groups) != 1:
|
|
114
126
|
raise SystemExit(f"{wire}: {len(groups)} pin sets on this probe - name one with --pins")
|
|
@@ -116,20 +128,21 @@ def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
|
|
|
116
128
|
|
|
117
129
|
|
|
118
130
|
def _plan_fn(hst, spec: str) -> tuple[int, dict[str, int]]:
|
|
119
|
-
"""fn and its role names (from the registry) for `spec`: an fn number, or name[#
|
|
131
|
+
"""fn and its role names (from the registry) for `spec`: an fn number, or name[#instance] (the list's instance,
|
|
132
|
+
0-based, core §7.2)."""
|
|
120
133
|
from . import registry as reg
|
|
121
134
|
if spec.isdigit():
|
|
122
135
|
fn = int(spec)
|
|
123
136
|
name = next((e.name for e in core.list_entries(hst) if e.fn == fn), "")
|
|
124
137
|
else:
|
|
125
138
|
name, _, k = spec.partition("#")
|
|
126
|
-
|
|
127
|
-
if not
|
|
139
|
+
entries = [e for e in core.list_entries(hst, name, exact=True)]
|
|
140
|
+
if not entries:
|
|
128
141
|
raise SystemExit(f"the probe offers no {name}")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
instance = int(k) if k else 0
|
|
143
|
+
fn = next((e.fn for e in entries if e.instance == instance), None)
|
|
144
|
+
if fn is None:
|
|
145
|
+
raise SystemExit(f"{name}: instances {sorted(e.instance for e in entries)}, not {instance}")
|
|
133
146
|
iface = reg.INTERFACES.get(name)
|
|
134
147
|
roles = dict(iface.enum.get("role", {})) if iface else {}
|
|
135
148
|
return fn, roles
|
|
@@ -179,6 +192,8 @@ def _config(args) -> int:
|
|
|
179
192
|
cfg = config.ProbeConfig(hst)
|
|
180
193
|
if args.action == "show":
|
|
181
194
|
return _show(hst, cfg, args.json)
|
|
195
|
+
if args.action == "state":
|
|
196
|
+
return _state(cfg, args.json)
|
|
182
197
|
if args.action == "slot":
|
|
183
198
|
fn = _wire_fn(hst, args.wire)
|
|
184
199
|
args.wire = args.wire or str(fn)
|
|
@@ -207,6 +222,15 @@ def _config(args) -> int:
|
|
|
207
222
|
raise SystemExit(f"{role}: not a role of fn {fn} (roles: {', '.join(sorted(roles)) or 'numbers only'})")
|
|
208
223
|
items.append(config.Plan(fn=fn, role=number, channel=int(ch, 0)))
|
|
209
224
|
_change(hst, cfg, items, args.save)
|
|
225
|
+
elif args.action == "uart":
|
|
226
|
+
fn, _ = _plan_fn(hst, args.fn)
|
|
227
|
+
from .fixture import FixtureUart
|
|
228
|
+
fmt = args.format.upper()
|
|
229
|
+
try:
|
|
230
|
+
byte = FixtureUart.format_byte(int(fmt[0]), fmt[1], int(fmt[2]))
|
|
231
|
+
except (KeyError, ValueError, IndexError):
|
|
232
|
+
raise SystemExit(f"--format {args.format}: want <data bits><parity><stop bits>, e.g. 8N1, 8E2, 7O1") from None
|
|
233
|
+
_change(hst, cfg, [config.Uart(fn=fn, baud=args.baud, format=byte)], args.save)
|
|
210
234
|
elif args.action == "label":
|
|
211
235
|
_change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
|
|
212
236
|
elif args.action == "idle":
|
|
@@ -231,42 +255,66 @@ def _config(args) -> int:
|
|
|
231
255
|
hst.link.close()
|
|
232
256
|
|
|
233
257
|
|
|
258
|
+
def _plain(o):
|
|
259
|
+
return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
|
|
260
|
+
else list(o)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _state_dict(st) -> dict:
|
|
264
|
+
return {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds")},
|
|
265
|
+
"slots": [_plain(s) for s in st.slots], "binds": [_plain(b) for b in st.binds]}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _state(cfg, as_json: bool) -> int:
|
|
269
|
+
"""The live state alone (op state, lock-free): what a monitor polls."""
|
|
270
|
+
st = cfg.state()
|
|
271
|
+
if as_json:
|
|
272
|
+
print(json.dumps(_state_dict(st), indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
|
|
273
|
+
return 0
|
|
274
|
+
why = f" ({st.unreadable})" if st.unreadable else ""
|
|
275
|
+
print(f"storage: {st.storage}{why}, saved hash 0x{st.saved_hash:08x}")
|
|
276
|
+
for s in st.slots:
|
|
277
|
+
print(f" slot {s.slot}: {s.state}" + (f", connection {s.connection}" if s.connection else "")
|
|
278
|
+
+ (f", tried at {s.last_try_at_ns / 1e9:.3f} s" if s.last_try_at_ns is not None else "")
|
|
279
|
+
+ (f", target_id {s.target_id[::-1].hex()}" if s.target_id else ""))
|
|
280
|
+
for b in st.binds:
|
|
281
|
+
print(f" port {b.port}: {b.mode}, {b.flow}" + (f", carrying {b.selected}" if b.selected is not None else ""))
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
234
285
|
def _show(hst, cfg, as_json: bool) -> int:
|
|
235
286
|
h, _ = cfg.get()
|
|
236
287
|
items = cfg.items()
|
|
288
|
+
decl = cfg.describe()
|
|
237
289
|
st = cfg.state()
|
|
238
290
|
kinds = {i: _name(k) for i, k, _ in core.transports(hst)}
|
|
239
291
|
if as_json:
|
|
240
|
-
|
|
241
|
-
return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
|
|
242
|
-
else list(o)
|
|
243
|
-
out = {"hash": h, "items": [dict(type=type(i).__name__, **plain(i)) if hasattr(i, "__dict__") else plain(i)
|
|
292
|
+
out = {"hash": h, "items": [dict(type=type(i).__name__, **_plain(i)) if hasattr(i, "__dict__") else _plain(i)
|
|
244
293
|
for i in items],
|
|
245
|
-
"
|
|
246
|
-
"slots": [plain(s) for s in st.slots], "binds": [plain(b) for b in st.binds]},
|
|
247
|
-
"transports": kinds}
|
|
294
|
+
"declared": _plain(decl), "state": _state_dict(st), "transports": kinds}
|
|
248
295
|
print(json.dumps(out, indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
|
|
249
296
|
return 0
|
|
250
|
-
|
|
297
|
+
why = f" ({st.unreadable})" if st.unreadable else ""
|
|
298
|
+
print(f"storage: {st.storage}{why} ({decl.storage_bytes} bytes), saved hash 0x{st.saved_hash:08x}; now 0x{h:08x}")
|
|
251
299
|
print("transports: " + ", ".join(f"{i} {k}" for i, k in kinds.items()))
|
|
252
300
|
by_slot = {s.slot: s for s in st.slots}
|
|
253
|
-
print(f"slots (up to {
|
|
301
|
+
print(f"slots (up to {decl.slots_max}):")
|
|
254
302
|
for it in items:
|
|
255
303
|
if isinstance(it, config.Slot):
|
|
256
304
|
s = by_slot.get(it.slot)
|
|
257
305
|
pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
|
|
258
|
-
retry = f" retry {it.retry_s} s" if it.attach == "at-boot" else ""
|
|
306
|
+
retry = f" retry {it.retry_s:g} s" if it.attach == "at-boot" else ""
|
|
259
307
|
retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
|
|
260
308
|
lock = (f" lock {int.from_bytes(it.lock[1], 'little'):08x}:{int.from_bytes(it.lock[2], 'little'):08x}"
|
|
261
309
|
if it.lock else "")
|
|
262
310
|
live = ""
|
|
263
311
|
if s:
|
|
264
|
-
tried = "never tried" if s.
|
|
312
|
+
tried = "never tried" if s.last_try_at_ns is None else f"tried at {s.last_try_at_ns / 1e9:.3f} s"
|
|
265
313
|
tid = f" target_id {s.target_id[::-1].hex()}" if s.target_id else ""
|
|
266
314
|
live = f" -> {s.state}" + (f" (connection {s.connection})" if s.connection else f" ({tried})") + tid
|
|
267
315
|
print(f" {it.slot} {it.name}: fn {it.wire_fn} pins {pins} {it.attach}{retry} {it.mechanism}{lock}{live}")
|
|
268
316
|
by_port = {b.port: b for b in st.binds}
|
|
269
|
-
print(f"binds (modes: {', '.join(
|
|
317
|
+
print(f"binds (modes: {', '.join(decl.bind_modes) or '-'}):")
|
|
270
318
|
names = {it.slot: it.name for it in items if isinstance(it, config.Slot)}
|
|
271
319
|
for it in items:
|
|
272
320
|
if isinstance(it, config.Bind):
|
oep_client/arm.py
CHANGED
|
@@ -29,17 +29,19 @@ class SwdWire(WireBase):
|
|
|
29
29
|
self.existing = False
|
|
30
30
|
|
|
31
31
|
def attach(self, targetsel: int | None = None, max_speed: int | None = None,
|
|
32
|
-
pins: tuple[int, int] | None = None) -> tuple[int, int, bool]:
|
|
33
|
-
"""-> (connection, DPIDR, woke from dormant).
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
pins: tuple[int, int] | None = None, reset: tuple[int, int] | None = None) -> tuple[int, int, bool]:
|
|
33
|
+
"""-> (connection, DPIDR, woke from dormant). The request is method 0 (swd has no halting attach) and TLVs:
|
|
34
|
+
max_speed (required, critical; None: the wire's declared max_clock_hz), targetsel (multidrop, critical), pins,
|
|
35
|
+
reset = (channel, hold_ms) (critical). self.existing: the wire was attached already (its connection returned).
|
|
36
|
+
self.flags: the attach_flags byte."""
|
|
37
|
+
body = bytes([0]) + self._speed_tlv(self._speed_or_default(max_speed)) + self._pins_tlv(pins) + self._reset_tlv(reset)
|
|
36
38
|
if targetsel is not None:
|
|
37
39
|
body += m.tlv(self.TAG_TARGETSEL, struct.pack("<I", targetsel), critical=True)
|
|
38
40
|
rd = m.Reader(self._call(self.ATTACH, body).payload)
|
|
39
|
-
conn, dpidr, flags, self.speed_hz = rd.take("HIBI")
|
|
40
|
-
self.existing = bool(flags &
|
|
41
|
+
conn, dpidr, self.flags, self.speed_hz = rd.take("HIBI")
|
|
42
|
+
self.existing = bool(self.flags & self.FLAGS["existing"])
|
|
41
43
|
rd.tail()
|
|
42
|
-
return conn, dpidr, bool(flags &
|
|
44
|
+
return conn, dpidr, bool(self.flags & self.FLAGS["dormant_woken"])
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
class AdiError(TargetError):
|
|
@@ -82,8 +84,8 @@ class ArmAdi(Interface):
|
|
|
82
84
|
reads = transfer_reads(steps)
|
|
83
85
|
r = self._request(self.TRANSFER, struct.pack("<H", len(reads)) + steps)
|
|
84
86
|
rd = ran(r)
|
|
85
|
-
done, status, self.last_ack = rd.take("
|
|
86
|
-
values = rd.words(
|
|
87
|
+
done, status, self.last_ack, nvals = rd.take("HBBH") # nvals: the values the answer carries (debug §6)
|
|
88
|
+
values = rd.words(nvals)
|
|
87
89
|
rd.tail()
|
|
88
90
|
if status != OK or not r.succeeded or done != len(reads):
|
|
89
91
|
raise AdiError(f"transfer (ack {self.last_ack:#x})", status, r, done=done, values=values)
|