oep-client-python 0.0.27__py3-none-any.whl → 0.0.29__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 +336 -56
- oep_client/arm.py +5 -2
- oep_client/capture.py +340 -59
- oep_client/catalog.py +75 -30
- oep_client/ch32_flash.py +6 -3
- oep_client/cobs.py +3 -3
- oep_client/config.py +361 -95
- oep_client/console.py +53 -18
- oep_client/core.py +202 -39
- oep_client/discovery.py +457 -0
- oep_client/dump.py +111 -22
- oep_client/endpoint.py +2107 -964
- oep_client/fixture.py +217 -60
- oep_client/frames.py +17 -8
- oep_client/host.py +384 -100
- oep_client/interfaces.py +91 -28
- oep_client/kept_session.py +162 -0
- oep_client/link.py +1247 -265
- oep_client/linktest.py +39 -28
- oep_client/message.py +83 -82
- oep_client/multirate.py +254 -0
- oep_client/names.py +12 -8
- oep_client/pins.py +634 -0
- oep_client/registry.py +67 -42
- oep_client/riscv.py +321 -58
- oep_client/speed_record.py +64 -21
- oep_client/targets.py +100 -0
- oep_client/uiapduino.py +13 -11
- oep_client/usb_stream.py +2 -2
- oep_client/virtual_bench.py +572 -0
- oep_client/virtual_bench_capture.py +688 -0
- oep_client/virtual_bench_mdns.py +286 -0
- oep_client/{fake_serial.py → virtual_bench_serial.py} +24 -9
- oep_client/virtual_bench_serve.py +802 -0
- oep_client_python-0.0.29.dist-info/METADATA +526 -0
- oep_client_python-0.0.29.dist-info/RECORD +43 -0
- oep_client/fake.py +0 -330
- oep_client/fake_capture.py +0 -493
- oep_client/fake_serve.py +0 -365
- oep_client_python-0.0.27.dist-info/METADATA +0 -191
- oep_client_python-0.0.27.dist-info/RECORD +0 -37
- {oep_client_python-0.0.27.dist-info → oep_client_python-0.0.29.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.27.dist-info → oep_client_python-0.0.29.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.27.dist-info → oep_client_python-0.0.29.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
"""The oep command: what a probe offers (dump) and its settings (config).
|
|
2
2
|
|
|
3
|
-
oep dump --port /run/board-identify/by-id/<probe> oep dump --
|
|
3
|
+
oep dump --port /run/board-identify/by-id/<probe> oep dump --virtual p4-x035 --prefix oep.target --json
|
|
4
4
|
oep config show <probe> (the settings and what the probe declares; lock-free)
|
|
5
5
|
oep config state <probe> (the live slot / bind / storage state; lock-free)
|
|
6
6
|
oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --retry 1 --mechanism dmseq
|
|
7
|
-
oep config bind <probe> --port 1 --
|
|
7
|
+
oep config bind <probe> --port 1 --stream slot:x035 (the one stream a serial port carries)
|
|
8
8
|
oep config plan <probe> oep.fixture.uart#1 rx=48 tx=49 (the fn's whole plan; fn number or name#instance)
|
|
9
9
|
oep config uart <probe> oep.fixture.uart#1 115200 --format 8N1
|
|
10
10
|
oep config disable <probe> 3 4 (channels the probe never uses or touches; remove disable CH re-enables)
|
|
11
11
|
oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
|
|
12
|
-
oep
|
|
13
|
-
|
|
12
|
+
oep config wifi <probe> --index 0 --ssid LAB --pass-prompt [--save] (a network the probe joins, probe.config §1.4;
|
|
13
|
+
the passphrase is asked without echo or read from --pass-env VAR, never
|
|
14
|
+
printed; without either the entry keeps its passphrase)
|
|
15
|
+
oep config wifi <probe> --from-env [--save] (OEP_WIFI_SSID_<n> / OEP_WIFI_PASS_<n>, n = the index)
|
|
16
|
+
oep config wifi-unset <probe> --index 0 [--save]
|
|
17
|
+
oep find [--timeout 2] [--json] [--no-verify] (probes announcing _oep._tcp by DNS-SD over mDNS: unit_id, host,
|
|
18
|
+
port, IP; each verified first by confirm + describe's unit_id, host guide
|
|
19
|
+
§4.1 - one that fails is dropped with a warning)
|
|
20
|
+
oep speed <probe> [--candidates 921600,500000] [--verify [--flows in:2,out:2]] (port_speed, oep-if-link §3 and the host
|
|
21
|
+
guide §17: try the candidates in order on a UART bridge, report; default
|
|
22
|
+
500000 - a faster rate only when named, after its 1 s verify each way)
|
|
14
23
|
oep linktest <probe> --rates now,921600 --patterns in,out,duplex --inflight 1,2 --sizes 128,496 --frames 300
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
oep pins <probe> --power 5 --wire swio (find the target's debug pins and reset line: classify the channels, scan,
|
|
25
|
+
identify, hold-low + attach under reset; prints a slot, --save writes it)
|
|
26
|
+
oep clock <probe> [-n 8] [--json] (fn 0's clock, core §7.7: the probe's uptime_ns and boot_id against this
|
|
27
|
+
host's time - the reading with the shortest round trip of n)
|
|
28
|
+
oep restart <probe> [--reopen-s S] (oep.probe.restart: the probe restarts; waits until it is back, its new boot_id)
|
|
29
|
+
|
|
30
|
+
<probe>: a serial port, tcp://HOST[:PORT] (no port: the one DNS-SD finds), tcp:UNIT_ID (found by DNS-SD) or
|
|
31
|
+
usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
|
|
17
32
|
ends the session after it; it takes effect at once, and stays over a restart only after `save` (or --save).
|
|
18
33
|
"""
|
|
19
34
|
|
|
@@ -25,29 +40,51 @@ import sys
|
|
|
25
40
|
import json
|
|
26
41
|
import struct
|
|
27
42
|
|
|
28
|
-
from . import catalog, config, core, dump,
|
|
43
|
+
from . import catalog, config, core, dump, virtual_bench, host, link
|
|
44
|
+
|
|
45
|
+
USB_HINT = ("install the usb / hid extras (pip install 'oep-client-python[usb-async,hid]') so every way in to the USB "
|
|
46
|
+
"device can be tried, or name the probe's port (a serial port such as /dev/ttyACM0 or COM3, tcp://HOST:PORT)")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _open_host(target: str, **kw):
|
|
50
|
+
"""link.open_host for a command: a probe that cannot be opened (no device, no port, not an OEP probe, a value the
|
|
51
|
+
host does not use) ends the command with one line on stderr instead of a traceback; for a usb target the line
|
|
52
|
+
says how to get another way in."""
|
|
53
|
+
try:
|
|
54
|
+
return link.open_host(target, **kw)
|
|
55
|
+
except (OSError, ValueError, ImportError, host.OepError) as e:
|
|
56
|
+
why = " ".join(str(e).split()).rstrip(".") or type(e).__name__
|
|
57
|
+
hint = f" Hint: {USB_HINT}." if isinstance(e, FileNotFoundError) and (target == "usb" or target.startswith("usb:")) \
|
|
58
|
+
else ""
|
|
59
|
+
raise SystemExit(f"oep: cannot open {target}: {why}.{hint}") from None
|
|
29
60
|
|
|
30
61
|
|
|
31
62
|
def main(argv=None) -> int:
|
|
32
63
|
parser = argparse.ArgumentParser(prog="oep", description="Open Embedded Probe: what a probe offers, its settings")
|
|
33
64
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
34
65
|
_config_parser(sub)
|
|
35
|
-
sp = sub.add_parser("speed", help="port_speed (
|
|
66
|
+
sp = sub.add_parser("speed", help="port_speed (oep-if-link §3, host guide §17): try faster rates on a UART bridge, "
|
|
36
67
|
"print the report")
|
|
37
68
|
sp.add_argument("probe", help="the probe's serial port (a UART bridge this host opens)")
|
|
38
69
|
sp.add_argument("rates", nargs="?", default="", help="the candidates, comma-separated, in order of preference "
|
|
39
70
|
"(the first that passes is kept; default 500000). Same as --candidates")
|
|
40
|
-
sp.add_argument("--candidates", default="", help="the candidates, comma-separated (default 500000
|
|
71
|
+
sp.add_argument("--candidates", default="", help="the candidates, comma-separated (default 500000, the default "
|
|
72
|
+
"ceiling of oep-if-link §3 obligation 7). A rate above 500000 is tried only when named here, and "
|
|
73
|
+
"is committed only after full frames ran 1 s each way (in, out; duplex too with --verify's "
|
|
74
|
+
"default flows) at it (host guide §17.3.3)")
|
|
41
75
|
sp.add_argument("--flows", default="", help="the flows to verify, comma-separated FLOW[:N] (in, out, duplex; N in "
|
|
42
76
|
"flight, 0 = the most the link keeps; default: all three at that N). Selects --verify")
|
|
43
77
|
form = sp.add_mutually_exclusive_group()
|
|
44
|
-
form.add_argument("--verify", action="store_true", help="the full form (guide §
|
|
78
|
+
form.add_argument("--verify", action="store_true", help="the full form (guide §17.3): baseline, the flows measured "
|
|
45
79
|
"at every candidate, a failed flow run again one at a time")
|
|
46
|
-
form.add_argument("--minimal", action="store_true", help="the minimal form (guide §
|
|
80
|
+
form.add_argument("--minimal", action="store_true", help="the minimal form (guide §17.2, the default): try, confirm, "
|
|
47
81
|
"commit, no measurement")
|
|
48
82
|
sp.add_argument("--frames", type=int, default=link.FLOW_FRAMES, help="frames per flow when verifying (default 16)")
|
|
49
83
|
sp.add_argument("--no-record", action="store_true", help="do not read or write the record of passed / failed rates "
|
|
50
|
-
"(~/.cache/oep-client/link-speed.json
|
|
84
|
+
"(~/.cache/oep-client/link-speed.json: a pass 30 days, a failure 1 day; on by default here, off "
|
|
85
|
+
"in the library)")
|
|
86
|
+
sp.add_argument("--max-tries", type=int, default=None, help="the most candidates tried after the record ordered "
|
|
87
|
+
"them (default: all)")
|
|
51
88
|
sp.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed (default 115200)")
|
|
52
89
|
sp.add_argument("--json", action="store_true")
|
|
53
90
|
lt = sub.add_parser("linktest", help="measure the link: traffic patterns at rates the host asks for, what breaks")
|
|
@@ -59,13 +96,47 @@ def main(argv=None) -> int:
|
|
|
59
96
|
lt.add_argument("--sizes", default="", help="comma-separated frame payload sizes (default: a whole frame)")
|
|
60
97
|
lt.add_argument("--frames", type=int, default=300, help="frames per cell (default 300)")
|
|
61
98
|
lt.add_argument("--seconds", type=float, default=None, help="per cell for this long instead of --frames")
|
|
62
|
-
lt.add_argument("--timeout", type=float, default=
|
|
99
|
+
lt.add_argument("--timeout", type=float, default=None, help="seconds to wait for one answer (default 0.3; 3 over "
|
|
100
|
+
"TCP, where Wi-Fi retransmissions stall answers for 1-4 s)")
|
|
63
101
|
lt.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed to open at (default 115200)")
|
|
64
102
|
lt.add_argument("--low-latency", choices=("on", "off"), default="on", help="the serial driver's low-latency mode")
|
|
65
103
|
lt.add_argument("--json", action="store_true", help="one JSON object per cell")
|
|
104
|
+
pn = sub.add_parser("pins", help="find where a target is wired: classify the channels, scan the wire, find the "
|
|
105
|
+
"reset line, suggest a slot (writes nothing without --save)")
|
|
106
|
+
pn.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
|
|
107
|
+
pn.add_argument("--power", type=int, help="the gpio channel that powers the target: switched off, then on, to "
|
|
108
|
+
"see which channels follow it (never touched without this)")
|
|
109
|
+
pn.add_argument("--exclude", default="", help="channels never to read, scan or hold, comma-separated")
|
|
110
|
+
pn.add_argument("--wire", choices=("swio", "rvswd", "swd"), default="swio")
|
|
111
|
+
pn.add_argument("--steps", default="classify,hold,scan,identify,reset,slot",
|
|
112
|
+
help="the steps to run, comma-separated (default all: classify, hold, scan, identify, reset, slot)")
|
|
113
|
+
pn.add_argument("--save", action="store_true", help="write the suggested slot (config set, then save)")
|
|
114
|
+
pn.add_argument("--slot", type=int, default=0, help="--save: the slot number (default 0)")
|
|
115
|
+
pn.add_argument("--name", help="--save: the slot's name (default: the target family, else 'target')")
|
|
116
|
+
pn.add_argument("--json", action="store_true", help="the report as JSON (the steps' lines go to stderr)")
|
|
117
|
+
ck = sub.add_parser("clock", help="the probe's clock (fn 0 clock, core §7.7) against this host's: lock-free")
|
|
118
|
+
ck.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
|
|
119
|
+
ck.add_argument("-n", type=int, default=8, help="readings; the one with the shortest round trip is kept (default 8)")
|
|
120
|
+
ck.add_argument("--json", action="store_true")
|
|
121
|
+
rs = sub.add_parser("restart", help="restart the probe (oep.probe.restart, optional) and wait until it is back")
|
|
122
|
+
rs.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
|
|
123
|
+
rs.add_argument("--force", action="store_true", help="take the lock from its holder")
|
|
124
|
+
rs.add_argument("--reopen-s", type=float, default=0.0, metavar="S",
|
|
125
|
+
help="when the probe is not back within its restart_max_ms, open it again for up to S more seconds "
|
|
126
|
+
"(your reopen: a host where the device returns late, e.g. WSL re-attaching it through usbipd)")
|
|
127
|
+
fd = sub.add_parser("find", help="probes on the local network that announce _oep._tcp (DNS-SD over mDNS)")
|
|
128
|
+
fd.add_argument("--timeout", type=float, default=2.0, help="seconds to listen for answers (default 2)")
|
|
129
|
+
fd.add_argument("--engine", choices=("auto", "zeroconf", "minimal"), default="auto",
|
|
130
|
+
help="python-zeroconf (the mdns extra) or this package's one-shot query; auto: zeroconf if installed")
|
|
131
|
+
fd.add_argument("--json", action="store_true")
|
|
132
|
+
fd.add_argument("--no-verify", action="store_true",
|
|
133
|
+
help="list every announced instance as it is (default: only those that answer confirm and whose "
|
|
134
|
+
"describe unit_id is their TXT unit_id, host guide §4.1)")
|
|
135
|
+
fd.add_argument("--verify-timeout", type=float, default=1.0, metavar="S",
|
|
136
|
+
help="seconds for each answer of the verify (default 1)")
|
|
66
137
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
67
138
|
src = d.add_mutually_exclusive_group(required=True)
|
|
68
|
-
src.add_argument("--
|
|
139
|
+
src.add_argument("--virtual", choices=sorted(virtual_bench.PROFILES), help="an in-process virtual bench (no hardware)")
|
|
69
140
|
src.add_argument("--port", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID] (lock-free reads only)")
|
|
70
141
|
d.add_argument("--prefix", default="", help="only names under this namespace (label boundaries)")
|
|
71
142
|
d.add_argument("--exact", action="store_true", help="the prefix is a whole name")
|
|
@@ -77,17 +148,121 @@ def main(argv=None) -> int:
|
|
|
77
148
|
return _speed(args)
|
|
78
149
|
if args.command == "linktest":
|
|
79
150
|
return _linktest(args)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
151
|
+
if args.command == "pins":
|
|
152
|
+
return _pins_cmd(args)
|
|
153
|
+
if args.command == "clock":
|
|
154
|
+
return _clock_cmd(args)
|
|
155
|
+
if args.command == "restart":
|
|
156
|
+
return _restart_cmd(args)
|
|
157
|
+
if args.command == "find":
|
|
158
|
+
return _find_cmd(args)
|
|
159
|
+
|
|
160
|
+
confirm = (0, 1)
|
|
161
|
+
if args.virtual:
|
|
162
|
+
call = virtual_bench.PROFILES[args.virtual]().call
|
|
83
163
|
else:
|
|
84
|
-
hst =
|
|
164
|
+
hst = _open_host(args.port)
|
|
85
165
|
call = lambda fn, op, payload: hst.request(fn, op, payload, locked=False).payload # noqa: E731
|
|
86
|
-
|
|
166
|
+
confirm = hst.confirm_range() # confirmed already: the revision in use (core §7.1)
|
|
167
|
+
caps = dump.collect(call, args.prefix, args.exact, confirm)
|
|
87
168
|
sys.stdout.write(dump.to_json(caps) + "\n" if args.json else dump.to_text(caps))
|
|
88
169
|
return 0
|
|
89
170
|
|
|
90
171
|
|
|
172
|
+
# ---- oep find ------------------------------------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
def _find_cmd(args) -> int:
|
|
175
|
+
"""DNS-SD browse for _oep._tcp (transports §3, host guide §4.1): what each instance says - its TXT unit_id, the SRV
|
|
176
|
+
host and port, the addresses. Each is verified first (discovery.verify: confirm and describe's unit_id, all at
|
|
177
|
+
once; no session opened) and one that fails is dropped with a warning on stderr; --no-verify lists them raw."""
|
|
178
|
+
from . import discovery
|
|
179
|
+
try:
|
|
180
|
+
found = discovery.browse(args.timeout, args.engine)
|
|
181
|
+
except ImportError:
|
|
182
|
+
raise SystemExit("oep find --engine zeroconf: install the mdns extra (pip install 'oep-client-python[mdns]')") \
|
|
183
|
+
from None
|
|
184
|
+
dropped = []
|
|
185
|
+
if not args.no_verify:
|
|
186
|
+
found, dropped = discovery.verify(found, args.verify_timeout)
|
|
187
|
+
for f, why in dropped:
|
|
188
|
+
print(f"warning: dropped {f.target or f.host or '?'} ({f.instance}, TXT unit_id {f.unit_id or 'none'}): not "
|
|
189
|
+
f"verified as an OEP probe - {why}", file=sys.stderr)
|
|
190
|
+
if args.json:
|
|
191
|
+
print(json.dumps([{"unit_id": f.unit_id, "instance": f.instance, "host": f.host, "port": f.port,
|
|
192
|
+
"addresses": f.addresses, "target": f.target} for f in found], indent=2))
|
|
193
|
+
return 0
|
|
194
|
+
if not found and dropped:
|
|
195
|
+
print(f"no verified probe: {len(dropped)} instance(s) of _oep._tcp dropped (above)", file=sys.stderr)
|
|
196
|
+
return 1
|
|
197
|
+
if not found:
|
|
198
|
+
print(f"no probe announces _oep._tcp within {args.timeout:g} s (mDNS stays on the local link: behind a NAT or "
|
|
199
|
+
"a router name the probe as tcp://HOST:PORT)", file=sys.stderr)
|
|
200
|
+
return 1
|
|
201
|
+
for f in found:
|
|
202
|
+
print(f"{f.unit_id or '(no unit_id)'} {f.host or '?'} port {f.port or '?'} "
|
|
203
|
+
f"{', '.join(f.addresses) or 'no address'} {f.target} ({f.instance})")
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---- oep clock / oep restart ---------------------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def _clock_cmd(args) -> int:
|
|
210
|
+
"""The shortest round trip of n clock readings (host guide §12): the probe's uptime_ns stands for the midpoint of
|
|
211
|
+
this host's send and receive times (time.monotonic_ns), within half the round trip."""
|
|
212
|
+
hst = _open_host(args.probe)
|
|
213
|
+
r = hst.clock_best(max(1, args.n))
|
|
214
|
+
out = {"boot_id": r.boot_id, "uptime_ns": r.uptime_ns, "host_before_ns": r.before_ns, "host_after_ns": r.after_ns,
|
|
215
|
+
"round_trip_ns": r.round_trip_ns, "host_ns": r.host_ns, "uncertainty_ns": r.uncertainty_ns}
|
|
216
|
+
if args.json:
|
|
217
|
+
print(json.dumps(out))
|
|
218
|
+
else:
|
|
219
|
+
print(f"boot_id 0x{r.boot_id:08X}, uptime {r.uptime_ns / 1e9:.6f} s at host monotonic {r.host_ns / 1e9:.6f} s "
|
|
220
|
+
f"+/- {r.uncertainty_ns / 1e3:.1f} us (round trip {r.round_trip_ns / 1e3:.1f} us, best of {args.n})")
|
|
221
|
+
return 0
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _restart_cmd(args) -> int:
|
|
225
|
+
hst = _open_host(args.probe)
|
|
226
|
+
try:
|
|
227
|
+
core.restart_fn(hst)
|
|
228
|
+
except LookupError:
|
|
229
|
+
raise SystemExit("oep: the probe offers no oep.probe.restart (an optional interface)") from None
|
|
230
|
+
core.take(hst, owner="oep restart", force=args.force)
|
|
231
|
+
before = hst.limits["boot_id"] if hst.limits else None
|
|
232
|
+
after = hst.restart_probe(reopen_s=args.reopen_s)
|
|
233
|
+
if hst.restart_reopened:
|
|
234
|
+
print(f"not back within restart_max_ms; reopened within --reopen-s {args.reopen_s:g}")
|
|
235
|
+
print(f"restarted: boot_id 0x{before:08X} -> 0x{after:08X}" if before is not None else f"restarted: boot_id 0x{after:08X}")
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ---- oep pins -----------------------------------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def _pins_cmd(args) -> int:
|
|
242
|
+
from . import pins
|
|
243
|
+
exclude = [int(x, 0) for x in args.exclude.replace(" ", "").split(",") if x]
|
|
244
|
+
say = (lambda t: print(t, file=sys.stderr, flush=True)) if args.json else (lambda t: print(t, flush=True)) # noqa: E731
|
|
245
|
+
hst = _open_host(args.probe)
|
|
246
|
+
try:
|
|
247
|
+
core.take(hst, 10000, owner="oep pins")
|
|
248
|
+
finder = pins.PinFinder(hst, wire=args.wire, power=args.power, exclude=exclude, say=say, probe=args.probe,
|
|
249
|
+
save=args.save, slot=args.slot, name=args.name)
|
|
250
|
+
report = finder.run(steps=tuple(s for s in args.steps.split(",") if s))
|
|
251
|
+
say(f"done in {report.elapsed_s:.1f} s")
|
|
252
|
+
if args.json:
|
|
253
|
+
print(json.dumps(report.as_dict(), indent=2))
|
|
254
|
+
try:
|
|
255
|
+
hst.end()
|
|
256
|
+
except host.OepError:
|
|
257
|
+
pass
|
|
258
|
+
except host.Rejected as e:
|
|
259
|
+
print(f"refused: {e}", file=sys.stderr)
|
|
260
|
+
return 2
|
|
261
|
+
finally:
|
|
262
|
+
hst.link.close()
|
|
263
|
+
return 0 if report.found else 1
|
|
264
|
+
|
|
265
|
+
|
|
91
266
|
# ---- oep linktest -------------------------------------------------------------------------------------------------
|
|
92
267
|
|
|
93
268
|
def _linktest(args) -> int:
|
|
@@ -97,8 +272,8 @@ def _linktest(args) -> int:
|
|
|
97
272
|
from . import linktest
|
|
98
273
|
ints = lambda text: [int(v) for v in text.split(",") if v.strip()] # noqa: E731
|
|
99
274
|
rates = [None if v.strip() in ("0", "now") else int(v) for v in args.rates.split(",") if v.strip()] or [None]
|
|
100
|
-
hst =
|
|
101
|
-
else
|
|
275
|
+
hst = _open_host(args.probe, baud=args.baud) if args.probe.startswith("/") or ":" not in args.probe \
|
|
276
|
+
else _open_host(args.probe)
|
|
102
277
|
stream = getattr(hst.link, "stream", None)
|
|
103
278
|
if hasattr(stream, "set_low_latency_mode"):
|
|
104
279
|
try:
|
|
@@ -147,11 +322,11 @@ def _speed(args) -> int:
|
|
|
147
322
|
candidates = [int(r) for r in text.replace(" ", "").split(",") if r] or list(link.DEFAULT_CANDIDATES)
|
|
148
323
|
flows = _flows(args.flows)
|
|
149
324
|
verify = True if args.verify or flows else (False if args.minimal else None)
|
|
150
|
-
hst =
|
|
325
|
+
hst = _open_host(args.probe, baud=args.baud)
|
|
151
326
|
try:
|
|
152
|
-
core.take(hst, 5000, owner="oep speed")
|
|
327
|
+
core.take(hst, max(5000, link.lease_for(candidates, flows, verify)), owner="oep speed")
|
|
153
328
|
report = link.raise_speed(hst, candidates, flows=flows, verify=verify, frames=args.frames,
|
|
154
|
-
record=not args.no_record)
|
|
329
|
+
record=not args.no_record, max_tries=args.max_tries)
|
|
155
330
|
if args.json:
|
|
156
331
|
sys.stdout.write(json.dumps(dataclasses.asdict(report), indent=2) + "\n")
|
|
157
332
|
else:
|
|
@@ -187,21 +362,18 @@ def _config_parser(sub) -> None:
|
|
|
187
362
|
help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
|
|
188
363
|
slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
|
|
189
364
|
help="rvswd: SWCLK while the line rests (the target's: low on CH32L103 / V203)")
|
|
190
|
-
slot.add_argument("--lock", help="MASK:VALUE (hex u32) the target_id (WCH DMI 0x7F) must match, e.g. ffffff0f:035e0600")
|
|
191
365
|
slot.add_argument("--save", action="store_true", help="save after the change")
|
|
192
|
-
bind = cs.add_parser("bind", help="
|
|
366
|
+
bind = cs.add_parser("bind", help="the one stream a serial port carries (set it again to change it)")
|
|
193
367
|
bind.add_argument("probe")
|
|
194
368
|
bind.add_argument("--port", type=int, required=True, help="the serial port (its transport index, see show)")
|
|
195
|
-
bind.add_argument("--
|
|
196
|
-
bind.add_argument("--stream", action="append", required=True, help="slot:NAME, slot:N or uart:FN (repeatable)")
|
|
197
|
-
bind.add_argument("--select", type=int, default=0, help="manual: the stream it carries (index in --stream)")
|
|
369
|
+
bind.add_argument("--stream", required=True, help="slot:NAME, slot:N or uart:FN")
|
|
198
370
|
bind.add_argument("--save", action="store_true")
|
|
199
371
|
plan = cs.add_parser("plan", help="the pins an interface keeps (its whole plan, kept as a setting)")
|
|
200
372
|
plan.add_argument("probe")
|
|
201
373
|
plan.add_argument("fn", help="an fn, or name#instance (the list instance, 0 = the first; #0 may be left out, core §7.2)")
|
|
202
374
|
plan.add_argument("roles", nargs="+", help="ROLE=CHANNEL, ROLE a number or the interface's role name (rx, tx, line...)")
|
|
203
375
|
plan.add_argument("--save", action="store_true")
|
|
204
|
-
label = cs.add_parser("label", help="name a channel (
|
|
376
|
+
label = cs.add_parser("label", help="name a channel (a setting, read with config get)")
|
|
205
377
|
label.add_argument("probe")
|
|
206
378
|
label.add_argument("channel", type=int)
|
|
207
379
|
label.add_argument("text")
|
|
@@ -210,6 +382,10 @@ def _config_parser(sub) -> None:
|
|
|
210
382
|
idle.add_argument("probe")
|
|
211
383
|
idle.add_argument("channel", type=int)
|
|
212
384
|
idle.add_argument("mode", choices=sorted(config.IDLE))
|
|
385
|
+
drive = idle.add_mutually_exclusive_group()
|
|
386
|
+
drive.add_argument("--drive-ma", type=int, help="output modes: the strongest drive level of about this many mA or "
|
|
387
|
+
"less, picked here from the gpio's describe drive_levels")
|
|
388
|
+
drive.add_argument("--drive-level", type=int, help="output modes: a drive level number of this probe")
|
|
213
389
|
idle.add_argument("--save", action="store_true")
|
|
214
390
|
dis = cs.add_parser("disable", help="channels the probe never uses or touches (not on this board)")
|
|
215
391
|
dis.add_argument("probe")
|
|
@@ -226,6 +402,23 @@ def _config_parser(sub) -> None:
|
|
|
226
402
|
rm.add_argument("kind", choices=sorted(config.ITEM))
|
|
227
403
|
rm.add_argument("key", type=int)
|
|
228
404
|
rm.add_argument("--save", action="store_true")
|
|
405
|
+
wifi = cs.add_parser("wifi", help="a Wi-Fi network the probe joins (the wifi item; the passphrase is never printed)")
|
|
406
|
+
wifi.add_argument("probe")
|
|
407
|
+
wifi.add_argument("--index", type=int, help="the entry (tried in index order; below the probe's wifi_max)")
|
|
408
|
+
wifi.add_argument("--ssid", help="the network's name (1-32 bytes)")
|
|
409
|
+
pw = wifi.add_mutually_exclusive_group()
|
|
410
|
+
pw.add_argument("--pass-prompt", action="store_true", help="ask for the passphrase (no echo)")
|
|
411
|
+
pw.add_argument("--pass-env", metavar="VAR", help="read the passphrase from environment variable VAR")
|
|
412
|
+
pw.add_argument("--open", action="store_true", help="no passphrase: an open network")
|
|
413
|
+
pw.add_argument("--from-env", action="store_true", help="every entry from OEP_WIFI_SSID_<n> / OEP_WIFI_PASS_<n> "
|
|
414
|
+
"(n = the index; no PASS: open); entries whose SSID and passphrase presence already match are not "
|
|
415
|
+
"sent again (a passphrase cannot be compared: --force sends them)")
|
|
416
|
+
wifi.add_argument("--force", action="store_true", help="--from-env: send every entry, passphrases included")
|
|
417
|
+
wifi.add_argument("--save", action="store_true")
|
|
418
|
+
wun = cs.add_parser("wifi-unset", help="remove Wi-Fi entries (the wifi item of these indexes)")
|
|
419
|
+
wun.add_argument("probe")
|
|
420
|
+
wun.add_argument("--index", type=int, action="append", required=True, help="an entry's index (repeatable)")
|
|
421
|
+
wun.add_argument("--save", action="store_true")
|
|
229
422
|
for name in ("save", "erase"):
|
|
230
423
|
cs.add_parser(name, help=f"{name} the stored settings").add_argument("probe")
|
|
231
424
|
|
|
@@ -298,13 +491,16 @@ def _change(hst, cfg, items, save: bool) -> None:
|
|
|
298
491
|
h = cfg.set(items)
|
|
299
492
|
print(f"set: hash 0x{h:08x}")
|
|
300
493
|
if save:
|
|
301
|
-
|
|
494
|
+
if cfg.needs_save():
|
|
495
|
+
print(f"saved: hash 0x{cfg.save():08x}")
|
|
496
|
+
else:
|
|
497
|
+
print("saved already (storage_hash is the settings' hash): not written")
|
|
302
498
|
finally:
|
|
303
499
|
hst.end()
|
|
304
500
|
|
|
305
501
|
|
|
306
502
|
def _config(args) -> int:
|
|
307
|
-
hst =
|
|
503
|
+
hst = _open_host(args.probe)
|
|
308
504
|
try:
|
|
309
505
|
cfg = config.ProbeConfig(hst)
|
|
310
506
|
if args.action == "show":
|
|
@@ -314,19 +510,13 @@ def _config(args) -> int:
|
|
|
314
510
|
if args.action == "slot":
|
|
315
511
|
fn = _wire_fn(hst, args.wire)
|
|
316
512
|
args.wire = args.wire or str(fn)
|
|
317
|
-
lock = None
|
|
318
|
-
if args.lock:
|
|
319
|
-
mask, _, value = args.lock.partition(":")
|
|
320
|
-
lock = (1, struct.pack("<I", int(mask, 16)), struct.pack("<I", int(value, 16)))
|
|
321
513
|
it = config.Slot(slot=args.slot, wire_fn=fn, pins=_pins(hst, fn, args.pins, args.wire), name=args.name,
|
|
322
|
-
attach=args.attach, retry_s=args.retry, mechanism=args.mechanism,
|
|
514
|
+
attach=args.attach, retry_s=args.retry, mechanism=args.mechanism,
|
|
323
515
|
max_speed=args.max_speed, idle_clock=args.idle_clock)
|
|
324
516
|
_change(hst, cfg, [it], args.save)
|
|
325
517
|
elif args.action == "bind":
|
|
326
518
|
slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
|
|
327
|
-
|
|
328
|
-
selected=args.select)
|
|
329
|
-
_change(hst, cfg, [it], args.save)
|
|
519
|
+
_change(hst, cfg, [config.Bind(port=args.port, stream=_stream(args.stream, slots))], args.save)
|
|
330
520
|
elif args.action == "plan":
|
|
331
521
|
fn, roles = _plan_fn(hst, args.fn)
|
|
332
522
|
items = []
|
|
@@ -351,11 +541,24 @@ def _config(args) -> int:
|
|
|
351
541
|
elif args.action == "label":
|
|
352
542
|
_change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
|
|
353
543
|
elif args.action == "idle":
|
|
354
|
-
|
|
544
|
+
from .fixture import Drive, Gpio
|
|
545
|
+
drive = Drive.level(args.drive_level) if args.drive_level is not None else None
|
|
546
|
+
if args.drive_ma is not None:
|
|
547
|
+
levels = Gpio(hst).drive_levels()
|
|
548
|
+
if levels is None:
|
|
549
|
+
raise SystemExit("--drive-ma: this probe declares no drive_levels (its strength cannot be chosen)")
|
|
550
|
+
drive = levels.at_most(args.drive_ma)
|
|
551
|
+
if drive is not None and args.mode not in ("output-low", "output-high"):
|
|
552
|
+
raise SystemExit(f"--drive-ma / --drive-level go with output-low / output-high, not {args.mode}")
|
|
553
|
+
_change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode, drive=drive)], args.save)
|
|
355
554
|
elif args.action == "disable":
|
|
356
555
|
_change(hst, cfg, [config.Disable(channel=ch) for ch in args.channels], args.save)
|
|
357
556
|
elif args.action == "remove":
|
|
358
557
|
_change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
|
|
558
|
+
elif args.action == "wifi":
|
|
559
|
+
_wifi_set(hst, cfg, args)
|
|
560
|
+
elif args.action == "wifi-unset":
|
|
561
|
+
_change(hst, cfg, [config.remove("wifi", i) for i in args.index], args.save)
|
|
359
562
|
else:
|
|
360
563
|
core.take(hst, 3000, owner="oep config")
|
|
361
564
|
try:
|
|
@@ -374,14 +577,68 @@ def _config(args) -> int:
|
|
|
374
577
|
hst.link.close()
|
|
375
578
|
|
|
376
579
|
|
|
580
|
+
def _wifi_set(hst, cfg, args) -> None:
|
|
581
|
+
"""oep config wifi (host guide §15.1): the passphrase from a prompt without echo or an environment variable, never
|
|
582
|
+
from the command line, never printed; without one the entry keeps its passphrase (pass_len 0xFF) - an entry that
|
|
583
|
+
does not exist yet needs --pass-prompt, --pass-env or --open."""
|
|
584
|
+
import os
|
|
585
|
+
decl = cfg.describe()
|
|
586
|
+
if config.ITEM["wifi"] not in decl.items:
|
|
587
|
+
raise SystemExit("this probe has no wifi item (describe's items)")
|
|
588
|
+
if args.from_env:
|
|
589
|
+
wanted = config.wifi_from_env(count=decl.wifi_max)
|
|
590
|
+
if not wanted:
|
|
591
|
+
raise SystemExit("--from-env: no OEP_WIFI_SSID_<n> set (n = 0 .. wifi_max - 1)")
|
|
592
|
+
have = {it.index: it for it in cfg.items() if isinstance(it, config.Wifi)}
|
|
593
|
+
send = [w for w in wanted if args.force or not config.same_items([have[w.index]] if w.index in have else [], [w])]
|
|
594
|
+
for w in wanted:
|
|
595
|
+
print(f"wifi {w.index}: " + ("sent" if w in send else "unchanged (ssid and passphrase presence match)"))
|
|
596
|
+
if send:
|
|
597
|
+
_change(hst, cfg, send, args.save)
|
|
598
|
+
elif args.save and cfg.needs_save():
|
|
599
|
+
core.take(hst, 3000, owner="oep config")
|
|
600
|
+
try:
|
|
601
|
+
print(f"saved: hash 0x{cfg.save():08x}")
|
|
602
|
+
finally:
|
|
603
|
+
hst.end()
|
|
604
|
+
return
|
|
605
|
+
if args.index is None or args.ssid is None:
|
|
606
|
+
raise SystemExit("oep config wifi: --index and --ssid (or --from-env)")
|
|
607
|
+
if not 0 <= args.index < max(decl.wifi_max, 1):
|
|
608
|
+
raise SystemExit(f"--index {args.index}: 0 to {decl.wifi_max - 1} (the probe's wifi_max is {decl.wifi_max})")
|
|
609
|
+
if args.pass_prompt:
|
|
610
|
+
import getpass
|
|
611
|
+
passphrase = getpass.getpass(f"passphrase for {args.ssid!r} (not shown): ")
|
|
612
|
+
elif args.pass_env:
|
|
613
|
+
passphrase = os.environ.get(args.pass_env)
|
|
614
|
+
if passphrase is None:
|
|
615
|
+
raise SystemExit(f"--pass-env {args.pass_env}: not set")
|
|
616
|
+
elif args.open:
|
|
617
|
+
passphrase = None
|
|
618
|
+
else:
|
|
619
|
+
if not any(isinstance(it, config.Wifi) and it.index == args.index for it in cfg.items()):
|
|
620
|
+
raise SystemExit(f"wifi entry {args.index} does not exist yet: give --pass-prompt, --pass-env VAR or --open")
|
|
621
|
+
passphrase = config.KEEP
|
|
622
|
+
it = config.Wifi(index=args.index, ssid=args.ssid, passphrase=passphrase)
|
|
623
|
+
try:
|
|
624
|
+
it.value()
|
|
625
|
+
except ValueError as e:
|
|
626
|
+
raise SystemExit(str(e)) from None
|
|
627
|
+
_change(hst, cfg, [it], args.save)
|
|
628
|
+
print(f"wifi {args.index}: {it.shown()['ssid']!r}, passphrase {it.shown()['passphrase']}")
|
|
629
|
+
|
|
630
|
+
|
|
377
631
|
def _plain(o):
|
|
632
|
+
if hasattr(o, "shown"):
|
|
633
|
+
return o.shown() # a wifi item: never its passphrase
|
|
378
634
|
return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
|
|
379
635
|
else list(o)
|
|
380
636
|
|
|
381
637
|
|
|
382
638
|
def _state_dict(st) -> dict:
|
|
383
|
-
return {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds")},
|
|
384
|
-
"slots": [_plain(s) for s in st.slots], "binds": [_plain(b) for b in st.binds]
|
|
639
|
+
return {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds", "wifi")},
|
|
640
|
+
"slots": [_plain(s) for s in st.slots], "binds": [_plain(b) for b in st.binds],
|
|
641
|
+
**({"wifi": vars(st.wifi)} if st.wifi is not None else {})}
|
|
385
642
|
|
|
386
643
|
|
|
387
644
|
def _state(cfg, as_json: bool) -> int:
|
|
@@ -394,13 +651,30 @@ def _state(cfg, as_json: bool) -> int:
|
|
|
394
651
|
print(f"storage: {st.storage}{why}, saved hash 0x{st.saved_hash:08x}")
|
|
395
652
|
for s in st.slots:
|
|
396
653
|
print(f" slot {s.slot}: {s.state}" + (f", connection {s.connection}" if s.connection else "")
|
|
397
|
-
+ (f", tried at {s.last_try_at_ns / 1e9:.3f} s" if s.last_try_at_ns is not None else "")
|
|
398
|
-
+ (f", target_id {s.target_id[::-1].hex()}" if s.target_id else ""))
|
|
654
|
+
+ (f", tried at {s.last_try_at_ns / 1e9:.3f} s" if s.last_try_at_ns is not None else ""))
|
|
399
655
|
for b in st.binds:
|
|
400
|
-
print(f" port {b.port}: {b.
|
|
656
|
+
print(f" port {b.port}: {b.flow}")
|
|
657
|
+
if st.wifi is not None:
|
|
658
|
+
print(f"wifi: {st.wifi.text()}")
|
|
401
659
|
return 0
|
|
402
660
|
|
|
403
661
|
|
|
662
|
+
def _tids(hst) -> dict[int, str]:
|
|
663
|
+
"""connection -> its target_id as hex, from every wire's connections (the slot's target is the host's to check,
|
|
664
|
+
probe.config §1.1, debug §2.1); best effort."""
|
|
665
|
+
from . import riscv
|
|
666
|
+
out = {}
|
|
667
|
+
for e in core.list_entries(hst):
|
|
668
|
+
if e.name in ("oep.wire.rvswd", "oep.wire.swio"):
|
|
669
|
+
try:
|
|
670
|
+
for c in riscv.Wire(hst, e.name).connections():
|
|
671
|
+
if c.target_id:
|
|
672
|
+
out[c.connection] = c.target_id[1][::-1].hex()
|
|
673
|
+
except host.OepError:
|
|
674
|
+
pass
|
|
675
|
+
return out
|
|
676
|
+
|
|
677
|
+
|
|
404
678
|
def _show(hst, cfg, as_json: bool) -> int:
|
|
405
679
|
h, _ = cfg.get()
|
|
406
680
|
items = cfg.items()
|
|
@@ -417,6 +691,7 @@ def _show(hst, cfg, as_json: bool) -> int:
|
|
|
417
691
|
print(f"storage: {st.storage}{why} ({decl.storage_bytes} bytes), saved hash 0x{st.saved_hash:08x}; now 0x{h:08x}")
|
|
418
692
|
print("transports: " + ", ".join(f"{i} {k}" for i, k in kinds.items()))
|
|
419
693
|
by_slot = {s.slot: s for s in st.slots}
|
|
694
|
+
tids = _tids(hst) if any(s.connection for s in st.slots) else {}
|
|
420
695
|
print(f"slots (up to {decl.slots_max}):")
|
|
421
696
|
for it in items:
|
|
422
697
|
if isinstance(it, config.Slot):
|
|
@@ -424,26 +699,31 @@ def _show(hst, cfg, as_json: bool) -> int:
|
|
|
424
699
|
pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
|
|
425
700
|
retry = f" retry {it.retry_s:g} s" if it.attach == "at-boot" else ""
|
|
426
701
|
retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
|
|
427
|
-
lock = (f" lock {int.from_bytes(it.lock[1], 'little'):08x}:{int.from_bytes(it.lock[2], 'little'):08x}"
|
|
428
|
-
if it.lock else "")
|
|
429
702
|
live = ""
|
|
430
703
|
if s:
|
|
431
704
|
tried = "never tried" if s.last_try_at_ns is None else f"tried at {s.last_try_at_ns / 1e9:.3f} s"
|
|
432
|
-
tid = f" target_id {s.
|
|
705
|
+
tid = f" target_id {tids[s.connection]}" if s.connection in tids else ""
|
|
433
706
|
live = f" -> {s.state}" + (f" (connection {s.connection})" if s.connection else f" ({tried})") + tid
|
|
434
|
-
print(f" {it.slot} {it.name}: fn {it.wire_fn} pins {pins} {it.attach}{retry} {it.mechanism}{
|
|
707
|
+
print(f" {it.slot} {it.name}: fn {it.wire_fn} pins {pins} {it.attach}{retry} {it.mechanism}{live}")
|
|
435
708
|
by_port = {b.port: b for b in st.binds}
|
|
436
|
-
print(
|
|
709
|
+
print("binds:")
|
|
437
710
|
names = {it.slot: it.name for it in items if isinstance(it, config.Slot)}
|
|
438
711
|
for it in items:
|
|
439
712
|
if isinstance(it, config.Bind):
|
|
440
713
|
b = by_port.get(it.port)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
live = f" -> {b.flow}"
|
|
444
|
-
print(f" port {it.port} ({kinds.get(it.port, '?')}): {
|
|
714
|
+
k, i = it.stream
|
|
715
|
+
stream = f"slot:{names.get(i, i)}" if k == "slot" else f"{k}:{i}"
|
|
716
|
+
live = f" -> {b.flow}" if b else ""
|
|
717
|
+
print(f" port {it.port} ({kinds.get(it.port, '?')}): {stream}{live}")
|
|
718
|
+
if config.ITEM["wifi"] in decl.items:
|
|
719
|
+
print(f"wifi (up to {decl.wifi_max}): " + (st.wifi.text() if st.wifi is not None else "no state"))
|
|
720
|
+
for it in items:
|
|
721
|
+
if isinstance(it, config.Wifi):
|
|
722
|
+
s = it.shown()
|
|
723
|
+
use = " <- in use" if st.wifi is not None and st.wifi.entry == it.index else ""
|
|
724
|
+
print(f" {s['index']} {s['ssid']!r}: passphrase {s['passphrase']}{use}")
|
|
445
725
|
for it in items:
|
|
446
|
-
if not isinstance(it, (config.Slot, config.Bind)):
|
|
726
|
+
if not isinstance(it, (config.Slot, config.Bind, config.Wifi)):
|
|
447
727
|
print(f" {it}")
|
|
448
728
|
return 0
|
|
449
729
|
|
oep_client/arm.py
CHANGED
|
@@ -21,6 +21,7 @@ _ADI = reg.TARGET_ARM_ADI
|
|
|
21
21
|
|
|
22
22
|
class SwdWire(WireBase):
|
|
23
23
|
NAME = "oep.wire.swd"
|
|
24
|
+
FLAGS = reg.WIRE_SWD.enum["attach_flags"] # existing, dormant_woken (0x01 / 0x08 are riscv's only)
|
|
24
25
|
TAG_TARGETSEL = reg.WIRE_SWD.tlv["attach"]["targetsel"]
|
|
25
26
|
|
|
26
27
|
def __init__(self, hst: h.Host):
|
|
@@ -37,10 +38,12 @@ class SwdWire(WireBase):
|
|
|
37
38
|
body = bytes([0]) + self._speed_tlv(self._speed_or_default(max_speed)) + self._pins_tlv(pins) + self._reset_tlv(reset)
|
|
38
39
|
if targetsel is not None:
|
|
39
40
|
body += m.tlv(self.TAG_TARGETSEL, struct.pack("<I", targetsel), critical=True)
|
|
40
|
-
rd = m.Reader(self._call(self.ATTACH, body, expect_ms=reset
|
|
41
|
+
rd = m.Reader(self._call(self.ATTACH, body, expect_ms=self.attach_ms(reset)).payload)
|
|
41
42
|
conn, dpidr, self.flags, self.speed_hz = rd.take("HIBI")
|
|
42
43
|
self.existing = bool(self.flags & self.FLAGS["existing"])
|
|
43
|
-
rd.tail()
|
|
44
|
+
tail = rd.tail()
|
|
45
|
+
tries = tail.get(reg.WIRE_SWD.tlv["attach_answer"]["search_retries"]) # oep-if-debug §1 (optional)
|
|
46
|
+
self.search_retries = struct.unpack_from("<H", tries)[0] if tries and len(tries) >= 2 else None
|
|
44
47
|
return conn, dpidr, bool(self.flags & self.FLAGS["dormant_woken"])
|
|
45
48
|
|
|
46
49
|
|