oep-client-python 0.0.23__py3-none-any.whl → 0.0.24__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 +38 -1
- oep_client/config.py +21 -5
- oep_client/core.py +3 -1
- oep_client/endpoint.py +230 -11
- oep_client/fake.py +3 -2
- oep_client/fake_serial.py +81 -2
- oep_client/fake_serve.py +14 -0
- oep_client/link.py +372 -11
- oep_client/registry.py +4 -4
- {oep_client_python-0.0.23.dist-info → oep_client_python-0.0.24.dist-info}/METADATA +31 -2
- {oep_client_python-0.0.23.dist-info → oep_client_python-0.0.24.dist-info}/RECORD +15 -15
- {oep_client_python-0.0.23.dist-info → oep_client_python-0.0.24.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.23.dist-info → oep_client_python-0.0.24.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.23.dist-info → oep_client_python-0.0.24.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
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
|
+
oep config disable <probe> 3 4 (channels the probe never uses or touches; remove disable CH re-enables)
|
|
10
11
|
oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
|
|
12
|
+
oep speed <probe> 1500000,921600,500000 (port_speed, core §3.5: try the rates in order on a UART bridge, report)
|
|
11
13
|
|
|
12
14
|
<probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
|
|
13
15
|
ends the session after it; it takes effect at once, and stays over a restart only after `save` (or --save).
|
|
@@ -28,6 +30,13 @@ def main(argv=None) -> int:
|
|
|
28
30
|
parser = argparse.ArgumentParser(prog="oep", description="Open Embedded Probe: what a probe offers, its settings")
|
|
29
31
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
30
32
|
_config_parser(sub)
|
|
33
|
+
sp = sub.add_parser("speed", help="port_speed (core §3.5): try faster rates on a UART bridge, print the report")
|
|
34
|
+
sp.add_argument("probe", help="the probe's serial port (a UART bridge this host opens)")
|
|
35
|
+
sp.add_argument("rates", help="comma-separated, in order of preference (the first that passes is kept)")
|
|
36
|
+
sp.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed (default 115200)")
|
|
37
|
+
sp.add_argument("--verify-bytes", type=int, default=32768)
|
|
38
|
+
sp.add_argument("--verify-s", type=float, default=1.0)
|
|
39
|
+
sp.add_argument("--json", action="store_true")
|
|
31
40
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
32
41
|
src = d.add_mutually_exclusive_group(required=True)
|
|
33
42
|
src.add_argument("--fake", choices=sorted(fake.PROFILES), help="in-process example probe")
|
|
@@ -38,6 +47,8 @@ def main(argv=None) -> int:
|
|
|
38
47
|
args = parser.parse_args(argv)
|
|
39
48
|
if args.command == "config":
|
|
40
49
|
return _config(args)
|
|
50
|
+
if args.command == "speed":
|
|
51
|
+
return _speed(args)
|
|
41
52
|
|
|
42
53
|
if args.fake:
|
|
43
54
|
call = fake.PROFILES[args.fake]().call
|
|
@@ -49,6 +60,26 @@ def main(argv=None) -> int:
|
|
|
49
60
|
return 0
|
|
50
61
|
|
|
51
62
|
|
|
63
|
+
# ---- oep speed ----------------------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def _speed(args) -> int:
|
|
66
|
+
"""Take the lock, raise_speed, print the report; the session's end puts the port back at its boot speed."""
|
|
67
|
+
import dataclasses
|
|
68
|
+
rates = [int(r) for r in args.rates.replace(" ", "").split(",") if r]
|
|
69
|
+
hst = link.open_host(args.probe, baud=args.baud)
|
|
70
|
+
try:
|
|
71
|
+
core.take(hst, 5000, owner="oep speed")
|
|
72
|
+
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s)
|
|
73
|
+
if args.json:
|
|
74
|
+
sys.stdout.write(json.dumps(dataclasses.asdict(report), indent=2) + "\n")
|
|
75
|
+
else:
|
|
76
|
+
sys.stdout.write(report.to_text())
|
|
77
|
+
hst.end()
|
|
78
|
+
finally:
|
|
79
|
+
hst.link.close()
|
|
80
|
+
return 0 if report.supported else 2
|
|
81
|
+
|
|
82
|
+
|
|
52
83
|
# ---- oep config ----------------------------------------------------------------------------------------------------
|
|
53
84
|
|
|
54
85
|
def _config_parser(sub) -> None:
|
|
@@ -98,13 +129,17 @@ def _config_parser(sub) -> None:
|
|
|
98
129
|
idle.add_argument("channel", type=int)
|
|
99
130
|
idle.add_argument("mode", choices=sorted(config.IDLE))
|
|
100
131
|
idle.add_argument("--save", action="store_true")
|
|
132
|
+
dis = cs.add_parser("disable", help="channels the probe never uses or touches (not on this board)")
|
|
133
|
+
dis.add_argument("probe")
|
|
134
|
+
dis.add_argument("channels", type=int, nargs="+")
|
|
135
|
+
dis.add_argument("--save", action="store_true")
|
|
101
136
|
uart = cs.add_parser("uart", help="a fixture UART's baud / format, applied whenever its plan has RX or TX")
|
|
102
137
|
uart.add_argument("probe")
|
|
103
138
|
uart.add_argument("fn", help="an fn, or name#instance of the oep.fixture.uart")
|
|
104
139
|
uart.add_argument("baud", type=int)
|
|
105
140
|
uart.add_argument("--format", default="8N1", help="data bits, parity, stop bits: 8N1 (default), 8E1, 7O2 ...")
|
|
106
141
|
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")
|
|
142
|
+
rm = cs.add_parser("remove", help="remove one item (unset): slot N, bind PORT, plan FN, label CH, idle CH, uart FN, disable CH")
|
|
108
143
|
rm.add_argument("probe")
|
|
109
144
|
rm.add_argument("kind", choices=sorted(config.ITEM))
|
|
110
145
|
rm.add_argument("key", type=int)
|
|
@@ -235,6 +270,8 @@ def _config(args) -> int:
|
|
|
235
270
|
_change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
|
|
236
271
|
elif args.action == "idle":
|
|
237
272
|
_change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode)], args.save)
|
|
273
|
+
elif args.action == "disable":
|
|
274
|
+
_change(hst, cfg, [config.Disable(channel=ch) for ch in args.channels], args.save)
|
|
238
275
|
elif args.action == "remove":
|
|
239
276
|
_change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
|
|
240
277
|
else:
|
oep_client/config.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""oep.probe.config revision 1 (oep-spec docs/oep-if-probe-config.ja.md): the probe's settings - plan, labels, idle
|
|
2
|
-
pins, slots, binds, fixture UART settings - read and set as items, removed with unset, saved when the host says so, and
|
|
2
|
+
pins, slots, binds, fixture UART settings, disabled channels - read and set as items, removed with unset, saved when the host says so, and
|
|
3
3
|
the live slot / bind / storage state as its own lock-free operation (describe is declarations only, core §7.3).
|
|
4
4
|
|
|
5
5
|
cfg = config.ProbeConfig(hst)
|
|
@@ -84,6 +84,20 @@ class Idle:
|
|
|
84
84
|
return struct.pack("<HB", self.channel, IDLE[self.mode])
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
@dataclass(kw_only=True)
|
|
88
|
+
class Disable:
|
|
89
|
+
"""A channel the probe never uses or touches (probe.config §1, item 0x07): not on this board, or wired to another
|
|
90
|
+
part. Any request naming it is refused unavailable (cause 5, held by settings); describe still declares it."""
|
|
91
|
+
channel: int
|
|
92
|
+
TAG = ITEM["disable"]
|
|
93
|
+
|
|
94
|
+
def key(self) -> tuple:
|
|
95
|
+
return (self.channel,)
|
|
96
|
+
|
|
97
|
+
def value(self) -> bytes:
|
|
98
|
+
return struct.pack("<H", self.channel)
|
|
99
|
+
|
|
100
|
+
|
|
87
101
|
@dataclass(kw_only=True)
|
|
88
102
|
class Slot:
|
|
89
103
|
"""A place a target is wired to (probe.config §1.1). pins: (swdio, swclk), swclk 0xFFFF on one wire (swio).
|
|
@@ -160,7 +174,7 @@ class Removal:
|
|
|
160
174
|
key: int
|
|
161
175
|
|
|
162
176
|
def encoded(self) -> bytes:
|
|
163
|
-
"""len(u8) tag(u8) key: the key is fn(u16) for plan / uart, channel(u16) for label / idle, slot(u8), port(u8)."""
|
|
177
|
+
"""len(u8) tag(u8) key: the key is fn(u16) for plan / uart, channel(u16) for label / idle / disable, slot(u8), port(u8)."""
|
|
164
178
|
tag = ITEM[self.kind]
|
|
165
179
|
key = bytes([self.key]) if self.kind in ("slot", "bind") else struct.pack("<H", self.key)
|
|
166
180
|
return bytes([1 + len(key), tag]) + key
|
|
@@ -172,7 +186,7 @@ def item(it) -> bytes:
|
|
|
172
186
|
|
|
173
187
|
def remove(kind: str, key: int) -> Removal:
|
|
174
188
|
"""The removal of the item of this key (for `unset`, or in a `set` list): kind plan (key fn: its whole plan), label
|
|
175
|
-
/ idle (channel), slot, bind (port), uart (fn)."""
|
|
189
|
+
/ idle / disable (channel), slot, bind (port), uart (fn)."""
|
|
176
190
|
if kind not in ITEM:
|
|
177
191
|
raise ValueError(f"no item kind {kind!r}")
|
|
178
192
|
return Removal(kind, key)
|
|
@@ -187,6 +201,8 @@ def decode(tag: int, v: bytes):
|
|
|
187
201
|
return Label(channel=struct.unpack_from("<H", v)[0], text=v[2:].decode("utf-8", "replace"))
|
|
188
202
|
if tag == ITEM["idle"] and len(v) >= 3:
|
|
189
203
|
return Idle(channel=struct.unpack_from("<H", v)[0], mode=_name(IDLE, v[2]))
|
|
204
|
+
if tag == ITEM["disable"] and len(v) >= 2:
|
|
205
|
+
return Disable(channel=struct.unpack_from("<H", v)[0])
|
|
190
206
|
if tag == ITEM["slot"] and len(v) >= SLOT_HEAD.size + 1:
|
|
191
207
|
n, wire_fn, swdio, swclk, attach, retry_ms, max_speed, idle, mech, name_len = SLOT_HEAD.unpack_from(v)
|
|
192
208
|
at = SLOT_HEAD.size
|
|
@@ -219,7 +235,7 @@ def decode(tag: int, v: bytes):
|
|
|
219
235
|
|
|
220
236
|
def _sort_key(tag: int, value: bytes) -> tuple:
|
|
221
237
|
"""The canonical order's key of one item (probe.config §2): tag, then the key - plan (fn, role, channel), label /
|
|
222
|
-
idle channel, slot, port, uart fn."""
|
|
238
|
+
idle / disable channel, slot, port, uart fn."""
|
|
223
239
|
if tag == ITEM["plan"] and len(value) >= 5:
|
|
224
240
|
return (tag,) + struct.unpack_from("<HBH", value)
|
|
225
241
|
if tag in (ITEM["slot"], ITEM["bind"]):
|
|
@@ -299,7 +315,7 @@ class ProbeConfig(Interface):
|
|
|
299
315
|
return h, out
|
|
300
316
|
|
|
301
317
|
def items(self) -> list:
|
|
302
|
-
"""The current settings, decoded (Plan, Label, Idle, Slot, Bind, Uart)."""
|
|
318
|
+
"""The current settings, decoded (Plan, Label, Idle, Slot, Bind, Uart, Disable)."""
|
|
303
319
|
return [decode(t, v) for t, v in self.get()[1]]
|
|
304
320
|
|
|
305
321
|
def set(self, items: list) -> int:
|
oep_client/core.py
CHANGED
|
@@ -140,7 +140,7 @@ def plan_apply(hst: h.Host, assignments: list[tuple[int, int, int]]) -> None:
|
|
|
140
140
|
|
|
141
141
|
class PinsTaken(h.Rejected):
|
|
142
142
|
"""plan_apply refused (unavailable) and the probe's settings say why: a pin kept by another fn's saved plan or by a
|
|
143
|
-
slot (oep.probe.config). `holders`: (channel, what holds it)."""
|
|
143
|
+
slot, or a channel the settings disable (oep.probe.config). `holders`: (channel, what holds it)."""
|
|
144
144
|
|
|
145
145
|
def __init__(self, result, holders: list[tuple[int, str]]):
|
|
146
146
|
super().__init__(result)
|
|
@@ -167,6 +167,8 @@ def _pin_holders(hst: h.Host, assignments: list[tuple[int, int, int]]) -> list[t
|
|
|
167
167
|
out.append((ch, f"the saved plan of fn {it.fn} (role {it.role})"))
|
|
168
168
|
elif isinstance(it, config.Slot) and ch in it.pins:
|
|
169
169
|
out.append((ch, f"slot {it.slot} {it.name}"))
|
|
170
|
+
elif isinstance(it, config.Disable) and it.channel == ch:
|
|
171
|
+
out.append((ch, "the settings (disabled; oep config remove <probe> disable <channel> enables it)"))
|
|
170
172
|
return out
|
|
171
173
|
|
|
172
174
|
|
oep_client/endpoint.py
CHANGED
|
@@ -28,10 +28,17 @@ define:
|
|
|
28
28
|
`FakeTarget` per pin pair (dmi / run answers count their values; run's stopped 2), oep.target.console streams (one
|
|
29
29
|
live stream per connection, lifetime by its users, the streams list), oep.fixture.gpio, oep.fixture.uart (its stream
|
|
30
30
|
made by the plan, status, the settings' uart item), and oep.probe.config (plan / label / idle / slot / bind / uart
|
|
31
|
-
items, get / set / unset / save / erase, the state op - describe is declarations only
|
|
31
|
+
/ disable items, get / set / unset / save / erase, the state op - describe is declarations only; a disabled channel
|
|
32
|
+
is refused everywhere with unavailable cause 5 and never parked: `parked` records the free pins' states it set)
|
|
32
33
|
- the serial ports' raw side (core §3.4, probe.config §1.2): `port_input` / `port_output` carry the bytes outside
|
|
33
34
|
the frames for each serial port by its bind; a port the lock holder's requests came in on is held until the
|
|
34
35
|
session ends, then resumes from the session's last host reset. The byte framing itself is `fake_serial`.
|
|
36
|
+
- port_speed (core §3.5, optional): on when the profile's describe declares it (`port_speed_base`, the boot speed;
|
|
37
|
+
None = off: the op is unknown_operation). try / commit / revert on the UART bridge the request came in on (else
|
|
38
|
+
unavailable cause 6), a rate the fake UART cannot make (outside 300..5000000) unsupported; a try reverts after
|
|
39
|
+
verify_ms without a commit or at a broken candidate, a commit after idle_ms with no good frame or 3 broken
|
|
40
|
+
candidates within 1 s, the session's end reverts after its answer. The line itself is modelled by `broken_rates`
|
|
41
|
+
(rate -> BrokenRate): frames at such a rate break, from a size and in the directions given (`fake_serial` applies it).
|
|
35
42
|
|
|
36
43
|
Every other non-core fn gets two stand-in operations so the session rules can be exercised - FAKE ONLY, they mean
|
|
37
44
|
nothing on a real probe: 0x01 write(u32) changes state, 0x02 read -> u32 needs no lock.
|
|
@@ -301,6 +308,23 @@ class Flow:
|
|
|
301
308
|
last_ms: int = 0
|
|
302
309
|
|
|
303
310
|
|
|
311
|
+
@dataclass
|
|
312
|
+
class BrokenRate:
|
|
313
|
+
"""How a line rate breaks frames in the fake (port_speed tests): frames of `min_size` bytes and more (on the wire)
|
|
314
|
+
break, towards the host and / or towards the probe. A broken frame towards the probe is a candidate whose CRC does
|
|
315
|
+
not match; towards the host its CRC is spoiled."""
|
|
316
|
+
min_size: int = 0
|
|
317
|
+
to_host: bool = True
|
|
318
|
+
to_probe: bool = True
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
PORT_SPEED_TAG = reg.CORE.tlv["describe"]["port_speed"]
|
|
322
|
+
OP_PORT_SPEED = reg.CORE.op["port_speed"]
|
|
323
|
+
SPEED_STEP = reg.CORE.enum["port_speed_step"]
|
|
324
|
+
SPEED_BAD_MAX, SPEED_BAD_WINDOW_MS = 3, 1000 # committed: this many broken candidates within the window revert
|
|
325
|
+
SPEED_RATES = (300, 5_000_000) # what the fake's UART makes (anything between, exactly)
|
|
326
|
+
|
|
327
|
+
|
|
304
328
|
class Endpoint:
|
|
305
329
|
MARKS_PER_ANSWER = 3 # small, so hosts must follow `more`
|
|
306
330
|
CHUNK = 64 # raw bytes a serial port takes at a time (probe guide §3.6)
|
|
@@ -387,6 +411,10 @@ class Endpoint:
|
|
|
387
411
|
self.bind_modes = struct.unpack_from("<I", cfg[CFG_DESCRIBE["bind_modes"]])[0] if CFG_DESCRIBE["bind_modes"] in cfg else 0
|
|
388
412
|
self.console_accept = 64
|
|
389
413
|
self.uart_accept = 256
|
|
414
|
+
# port_speed (core §3.5): on when the profile declares it; the boot speed every revert goes back to
|
|
415
|
+
self.port_speed_base: int | None = 115200 if any(t[0] == PORT_SPEED_TAG for t in self.static.get(0, ())) else None
|
|
416
|
+
self.broken_rates: dict[int, BrokenRate] = {} # the line: rates that break frames (fake_serial applies it)
|
|
417
|
+
self._transport = 0 # the transport the request being handled came in on
|
|
390
418
|
self._boot()
|
|
391
419
|
|
|
392
420
|
def _boot(self) -> None:
|
|
@@ -438,8 +466,49 @@ class Endpoint:
|
|
|
438
466
|
self.mixed_out: dict[int, bytearray] = {}
|
|
439
467
|
self.held_ports: set[int] = set()
|
|
440
468
|
self.session_resets: dict[tuple[int, int], tuple[object, int]] = {} # stream key -> (sid, position)
|
|
469
|
+
self.parked: dict[int, int] = {} # channel -> the idle mode the probe put the free pin in
|
|
470
|
+
self.speed_state = "base" # port_speed: "base", "try" or "committed" (core §3.5)
|
|
471
|
+
self.speed_port: int | None = None # the port off its boot speed
|
|
472
|
+
self.speed_rate = 0 # the rate it runs at
|
|
473
|
+
self.speed_asked = 0 # the baud the try asked (the commit names it again)
|
|
474
|
+
self.speed_until_ms = 0 # try: the commit's deadline (verify_ms)
|
|
475
|
+
self.speed_idle_ms = 0 # committed: revert after this long with no good frame (0 never)
|
|
476
|
+
self.speed_good_ms = 0 # the last good frame on that port
|
|
477
|
+
self.speed_bad: list[int] = [] # committed: the times of the recent broken candidates
|
|
478
|
+
self.speed_pending: tuple | None = None # ("switch", port, baud, verify_ms) / ("revert",): after the answer
|
|
479
|
+
self.speed_log: list[tuple[int, int]] = [] # (port, rate) every switch, reverts included
|
|
441
480
|
if self.saved is not None:
|
|
442
|
-
self._apply_saved()
|
|
481
|
+
self._apply_saved() # the saved disable items first: those pins are never parked
|
|
482
|
+
self._park(self._all_channels()) # then the free pins' idle state (probe.config §2 boot order)
|
|
483
|
+
|
|
484
|
+
@property
|
|
485
|
+
def disabled(self) -> set[int]:
|
|
486
|
+
"""The channels the settings' disable items take away (probe.config §1): never used, driven or configured."""
|
|
487
|
+
return {key for tag, key in self.config if tag == ITEM["disable"]}
|
|
488
|
+
|
|
489
|
+
def _refuse_disabled(self, channels, extra: bytes = b"") -> None:
|
|
490
|
+
"""A request naming a disabled channel: rejected unavailable cause 5 (held by settings) with the channel."""
|
|
491
|
+
for ch in channels:
|
|
492
|
+
if ch != 0xFFFF and ch in self.disabled:
|
|
493
|
+
raise unavailable("held_by_settings", ch, holder_kind="disabled", extra=extra)
|
|
494
|
+
|
|
495
|
+
def _all_channels(self) -> set[int]:
|
|
496
|
+
"""Every channel some fn's describe offers (role_channels, channel_group, the wires' pairs and reset lines)."""
|
|
497
|
+
out = {ch for fn in self.static if fn != m.CORE_FN for ch in self._declared_channels(fn)}
|
|
498
|
+
out |= {p for pairs in self.pairs.values() for pair in pairs for p in pair}
|
|
499
|
+
out |= {ch for chs in self.reset_channels.values() for ch in chs}
|
|
500
|
+
out.discard(0xFFFF)
|
|
501
|
+
return out
|
|
502
|
+
|
|
503
|
+
def _park(self, channels) -> None:
|
|
504
|
+
"""Free pins go to their idle state (the idle item, else Hi-Z) at boot and whenever released (probe.config §1);
|
|
505
|
+
a disabled channel stays as the reset left it (never parked), a held one is not free."""
|
|
506
|
+
busy = self._held()
|
|
507
|
+
for ch in channels:
|
|
508
|
+
if ch == 0xFFFF or ch in self.disabled or ch in busy:
|
|
509
|
+
continue
|
|
510
|
+
item = self.config.get((ITEM["idle"], ch))
|
|
511
|
+
self.parked[ch] = item[2] if item else 0 # 0 = Hi-Z
|
|
443
512
|
|
|
444
513
|
@property
|
|
445
514
|
def target_id(self) -> int | None:
|
|
@@ -676,6 +745,7 @@ class Endpoint:
|
|
|
676
745
|
return None
|
|
677
746
|
req = m.Request.unpack(data)
|
|
678
747
|
self.requests.append(req)
|
|
748
|
+
self._transport = transport
|
|
679
749
|
remembered = self._resent(req)
|
|
680
750
|
if remembered is not None:
|
|
681
751
|
return remembered
|
|
@@ -696,6 +766,8 @@ class Endpoint:
|
|
|
696
766
|
out = m.Result(req.corr, res, detail, payload).pack()
|
|
697
767
|
if req.session is not None and req.session == self.last:
|
|
698
768
|
self._remember(req, out)
|
|
769
|
+
if transport not in self.serial_ports:
|
|
770
|
+
self.speed_after_answer() # the answer is not on a port whose speed changes
|
|
699
771
|
return out
|
|
700
772
|
|
|
701
773
|
def _interface(self, fn: int):
|
|
@@ -732,6 +804,8 @@ class Endpoint:
|
|
|
732
804
|
return self._open(req.payload)
|
|
733
805
|
if req.fn != m.CORE_FN and req.fn not in self.names:
|
|
734
806
|
return m.REJECTED, m.UNKNOWN_FUNCTION, b""
|
|
807
|
+
if req.fn == m.CORE_FN and req.op == OP_PORT_SPEED and self.port_speed_base is None:
|
|
808
|
+
return m.REJECTED, m.UNKNOWN_OPERATION, b"" # the optional feature off (core §3.5)
|
|
735
809
|
if not self._lock_free(req.fn, req.op):
|
|
736
810
|
refused = self._check(req.session)
|
|
737
811
|
if refused:
|
|
@@ -919,6 +993,8 @@ class Endpoint:
|
|
|
919
993
|
if op == m.OP_KEEPALIVE:
|
|
920
994
|
_, ignored = t.tail()
|
|
921
995
|
return self._answer(b"", ignored)
|
|
996
|
+
if op == OP_PORT_SPEED:
|
|
997
|
+
return self._port_speed(t)
|
|
922
998
|
if op == m.OP_SUBSCRIBE:
|
|
923
999
|
fn, _min_bytes, max_delay_ms = t.take("HHI") # max_delay_ms u32 (core §11.3)
|
|
924
1000
|
t.tail()
|
|
@@ -959,6 +1035,7 @@ class Endpoint:
|
|
|
959
1035
|
raise unavailable("held_by_settings", holder_fn=min(named & self.plan_from_config),
|
|
960
1036
|
holder_kind="settings_plan")
|
|
961
1037
|
self._check_plan(got)
|
|
1038
|
+
self._refuse_disabled(ch for _, _, ch in got) # a disabled channel (probe.config §1): cause 5
|
|
962
1039
|
if self.plan_roles is not None and len([a for a in self.plan if a[0] not in named]) + len(got) > self.plan_roles:
|
|
963
1040
|
raise unavailable("limit") # plan_roles (core §8)
|
|
964
1041
|
self.plan = {a for a in self.plan if a[0] not in named} | set(got)
|
|
@@ -1034,11 +1111,14 @@ class Endpoint:
|
|
|
1034
1111
|
return out
|
|
1035
1112
|
|
|
1036
1113
|
def _drop_plan(self, fn: int) -> None:
|
|
1114
|
+
released = set()
|
|
1037
1115
|
for a in [a for a in self.plan if a[0] == fn]:
|
|
1038
1116
|
self.gpio_modes.pop(a[2], None)
|
|
1039
1117
|
self.plan.discard(a)
|
|
1118
|
+
released.add(a[2])
|
|
1040
1119
|
self.plan_from_config.discard(fn)
|
|
1041
1120
|
self._uart_plan_changed(fn)
|
|
1121
|
+
self._park(released)
|
|
1042
1122
|
|
|
1043
1123
|
# ---- oep.fixture.uart's stream: made by the plan, gone with it (fixture §2) ---------------------
|
|
1044
1124
|
def _uart_plan_changed(self, fn: int) -> None:
|
|
@@ -1097,9 +1177,96 @@ class Endpoint:
|
|
|
1097
1177
|
# ---- describe: the static declarations plus the live ones -----------------------------------
|
|
1098
1178
|
def _declarations(self, fn: int) -> list[bytes]:
|
|
1099
1179
|
"""describe: the profile's declarations as they are (core §7.3: nothing that changes - the firmware's labels
|
|
1100
|
-
only, the settings' are read by get; probe.config's state is its op state).
|
|
1180
|
+
only, the settings' are read by get; probe.config's state is its op state). fn 0's port_speed follows
|
|
1181
|
+
`port_speed_base` (a test turns the feature off or on)."""
|
|
1182
|
+
if fn == m.CORE_FN:
|
|
1183
|
+
out = [t for t in self.static[fn] if t[0] != PORT_SPEED_TAG]
|
|
1184
|
+
return out + [catalog.u8(PORT_SPEED_TAG, 1)] if self.port_speed_base is not None else out
|
|
1101
1185
|
return list(self.static[fn])
|
|
1102
1186
|
|
|
1187
|
+
# ---- port_speed (core §3.5) -----------------------------------------------------------------
|
|
1188
|
+
def _port_speed(self, t: "Take") -> tuple[int, int, bytes]:
|
|
1189
|
+
"""port(u8) baud(u32) step(u8) verify_ms(u16) idle_ms(u32) [TLV] -> baud(u32): the rate that applies."""
|
|
1190
|
+
port, baud, step, verify_ms, idle_ms = t.take("BIBHI")
|
|
1191
|
+
_, ignored = t.tail()
|
|
1192
|
+
if step not in SPEED_STEP.values():
|
|
1193
|
+
raise unsupported_fixed()
|
|
1194
|
+
if port != self._transport or port >= len(self.transports) or \
|
|
1195
|
+
self.transports[port] != fake.TRANSPORT["uart_bridge"]:
|
|
1196
|
+
raise unavailable("wrong_state") # only the UART bridge the request came in on
|
|
1197
|
+
if step == SPEED_STEP["try"]:
|
|
1198
|
+
if not SPEED_RATES[0] <= baud <= SPEED_RATES[1]:
|
|
1199
|
+
raise unsupported_fixed() # a baud this UART cannot make
|
|
1200
|
+
self.speed_pending = ("switch", port, baud, verify_ms)
|
|
1201
|
+
return self._answer(struct.pack("<I", baud), ignored)
|
|
1202
|
+
if step == SPEED_STEP["commit"]:
|
|
1203
|
+
if self.speed_state == "base" or port != self.speed_port or baud != self.speed_asked:
|
|
1204
|
+
raise unavailable("wrong_state") # nothing tried, or another baud
|
|
1205
|
+
self.speed_state = "committed"
|
|
1206
|
+
self.speed_idle_ms = idle_ms
|
|
1207
|
+
self.speed_good_ms = self.now()
|
|
1208
|
+
self.speed_bad.clear()
|
|
1209
|
+
return self._answer(struct.pack("<I", self.speed_rate), ignored)
|
|
1210
|
+
self.speed_pending = ("revert",)
|
|
1211
|
+
return self._answer(struct.pack("<I", self.port_speed_base), ignored)
|
|
1212
|
+
|
|
1213
|
+
def port_baud(self, port: int) -> int:
|
|
1214
|
+
"""The rate serial port `port` runs at now (its boot speed, or what port_speed set)."""
|
|
1215
|
+
if self.speed_state != "base" and port == self.speed_port:
|
|
1216
|
+
return self.speed_rate
|
|
1217
|
+
return self.port_speed_base or 115200
|
|
1218
|
+
|
|
1219
|
+
def breaks(self, port: int, size: int, to_host: bool) -> bool:
|
|
1220
|
+
"""The line model: a frame of `size` bytes on `port` at its rate now breaks (`broken_rates`)."""
|
|
1221
|
+
b = self.broken_rates.get(self.port_baud(port))
|
|
1222
|
+
return b is not None and size >= b.min_size and (b.to_host if to_host else b.to_probe)
|
|
1223
|
+
|
|
1224
|
+
def speed_after_answer(self) -> None:
|
|
1225
|
+
"""The answer that asked for a switch or a revert is out (at the old speed): now do it."""
|
|
1226
|
+
pending, self.speed_pending = self.speed_pending, None
|
|
1227
|
+
if pending is None:
|
|
1228
|
+
return
|
|
1229
|
+
if pending[0] == "revert":
|
|
1230
|
+
self._speed_revert()
|
|
1231
|
+
return
|
|
1232
|
+
_, port, baud, verify_ms = pending
|
|
1233
|
+
self.speed_state, self.speed_port, self.speed_rate, self.speed_asked = "try", port, baud, baud
|
|
1234
|
+
self.speed_until_ms = self.now() + verify_ms
|
|
1235
|
+
self.speed_good_ms = self.now()
|
|
1236
|
+
self.speed_bad.clear()
|
|
1237
|
+
self.speed_log.append((port, baud))
|
|
1238
|
+
|
|
1239
|
+
def _speed_revert(self) -> None:
|
|
1240
|
+
if self.speed_state == "base":
|
|
1241
|
+
return
|
|
1242
|
+
self.speed_log.append((self.speed_port, self.port_speed_base))
|
|
1243
|
+
self.speed_state, self.speed_port, self.speed_rate, self.speed_asked = "base", None, 0, 0
|
|
1244
|
+
self.speed_bad.clear()
|
|
1245
|
+
|
|
1246
|
+
def speed_frame(self, port: int, good: bool) -> None:
|
|
1247
|
+
"""A candidate closed on serial port `port`: a frame (good) or not (a broken candidate)."""
|
|
1248
|
+
if self.speed_state == "base" or port != self.speed_port:
|
|
1249
|
+
return
|
|
1250
|
+
if good:
|
|
1251
|
+
self.speed_good_ms = self.now()
|
|
1252
|
+
return
|
|
1253
|
+
if self.speed_state == "try":
|
|
1254
|
+
self._speed_revert() # the new speed breaks frames: not this one
|
|
1255
|
+
return
|
|
1256
|
+
now = self.now()
|
|
1257
|
+
self.speed_bad = [t for t in self.speed_bad if now - t < SPEED_BAD_WINDOW_MS] + [now]
|
|
1258
|
+
if len(self.speed_bad) >= SPEED_BAD_MAX:
|
|
1259
|
+
self._speed_revert()
|
|
1260
|
+
|
|
1261
|
+
def _speed_tick(self) -> None:
|
|
1262
|
+
if self.speed_pending and self.speed_pending[0] == "revert":
|
|
1263
|
+
self.speed_after_answer()
|
|
1264
|
+
now = self.now()
|
|
1265
|
+
if self.speed_state == "try" and now >= self.speed_until_ms:
|
|
1266
|
+
self._speed_revert() # no commit within verify_ms
|
|
1267
|
+
elif self.speed_state == "committed" and self.speed_idle_ms and now - self.speed_good_ms >= self.speed_idle_ms:
|
|
1268
|
+
self._speed_revert()
|
|
1269
|
+
|
|
1103
1270
|
def _page(self, tlvs: list[bytes], first: int) -> bytes:
|
|
1104
1271
|
budget = self.probe.max_frame - m.RESULT_HEADER - 1
|
|
1105
1272
|
out, sent = b"", 0
|
|
@@ -1129,12 +1296,13 @@ class Endpoint:
|
|
|
1129
1296
|
raise Reject(m.MALFORMED)
|
|
1130
1297
|
live = [c.pair for c in self.conns.values() if c.fn == fn]
|
|
1131
1298
|
full = len(live) >= self.max_connections.get(fn, 1) # every seat taken: the live pairs only
|
|
1299
|
+
self._refuse_disabled(ch for p in pairs if self._allows(fn, p) for ch in p) # cause 5 (probe.config §1)
|
|
1132
1300
|
if any(not self._allows(fn, p) or set(p) & self._held(fn, p) or (full and p not in live) for p in pairs):
|
|
1133
1301
|
raise Reject(m.UNAVAILABLE) # not allowed, held (§8.1), or no seat to try it on
|
|
1134
1302
|
if not pairs: # the count-0 list, from `skip` on (oep-if-debug §1)
|
|
1135
1303
|
skip = struct.unpack("<H", skip_tlv)[0] if skip_tlv is not None else 0
|
|
1136
|
-
pairs = [p for p in self._allowed_pairs(fn)
|
|
1137
|
-
|
|
1304
|
+
pairs = [p for p in self._allowed_pairs(fn) if not set(p) & self.disabled # disabled: not listed
|
|
1305
|
+
and not set(p) & self._held(fn, p) and (not full or p in live)][skip:]
|
|
1138
1306
|
pairs = pairs[:255] # tried is a u8
|
|
1139
1307
|
found = []
|
|
1140
1308
|
for p in pairs:
|
|
@@ -1164,6 +1332,7 @@ class Endpoint:
|
|
|
1164
1332
|
raise Reject(m.UNSUPPORTED, bytes([_T_ATTACH["reset"] | m.TAG_CRITICAL])) # not a reset line here (§3)
|
|
1165
1333
|
if hold_ms > self.max_op_ms:
|
|
1166
1334
|
raise Reject(m.UNSUPPORTED, bytes([_T_ATTACH["reset"] | m.TAG_CRITICAL])) # longer than one request may take
|
|
1335
|
+
self._refuse_disabled([channel]) # a disabled reset line: cause 5 (probe.config §1)
|
|
1167
1336
|
if any(a[2] == channel for a in self.plan):
|
|
1168
1337
|
raise unavailable("pin_in_use", channel, next(a[0] for a in self.plan if a[2] == channel), "plan")
|
|
1169
1338
|
idle_clock = idle_tlv[0] if idle_tlv is not None else 0
|
|
@@ -1241,13 +1410,17 @@ class Endpoint:
|
|
|
1241
1410
|
pair = struct.unpack("<HH", got[0x03])
|
|
1242
1411
|
if not self._allows(fn, pair):
|
|
1243
1412
|
raise Reject(m.UNSUPPORTED, bytes([_T_ATTACH["pins"] | m.TAG_CRITICAL])) # not a pair this wire offers (core §4.3 order 6)
|
|
1413
|
+
self._refuse_disabled(pair) # a disabled channel: cause 5 (probe.config §1)
|
|
1244
1414
|
if set(pair) & self._held(fn, pair):
|
|
1245
1415
|
raise unavailable("pin_in_use", next(iter(set(pair) & self._held(fn, pair)))) # held (§8.1)
|
|
1246
1416
|
return pair
|
|
1247
1417
|
live = [c.pair for c in self.conns.values() if c.fn == fn]
|
|
1248
1418
|
if len(live) == 1:
|
|
1249
1419
|
return live[0] # no pins: the wire's one live connection
|
|
1250
|
-
|
|
1420
|
+
offered = self._allowed_pairs(fn)
|
|
1421
|
+
allowed = [p for p in offered if not set(p) & self.disabled] # the probe never picks a disabled channel
|
|
1422
|
+
if not live and len(offered) == 1 and not allowed:
|
|
1423
|
+
self._refuse_disabled(offered[0]) # its one pair is disabled: cause 5
|
|
1251
1424
|
if live or len(allowed) != 1:
|
|
1252
1425
|
raise Reject(m.UNAVAILABLE) # the host chooses among several
|
|
1253
1426
|
return allowed[0]
|
|
@@ -1281,8 +1454,10 @@ class Endpoint:
|
|
|
1281
1454
|
|
|
1282
1455
|
def _close_conn(self, cid: int, mark: int) -> None:
|
|
1283
1456
|
"""The connection goes; its streams close (mark `mark`, then closed 4) and stay readable (console §2)."""
|
|
1284
|
-
self.conns.pop(cid, None)
|
|
1457
|
+
gone = self.conns.pop(cid, None)
|
|
1285
1458
|
self.resources.pop(cid, None)
|
|
1459
|
+
if gone is not None:
|
|
1460
|
+
self._park(gone.pair) # its pins are free again: their idle state
|
|
1286
1461
|
for (c, _), sid in list(self.stream_keys.items()):
|
|
1287
1462
|
if c == cid and not self.streams[sid].closed:
|
|
1288
1463
|
self.streams[sid].add_mark(mark, self.now_ns())
|
|
@@ -1612,6 +1787,7 @@ class Endpoint:
|
|
|
1612
1787
|
if not self.gpio_allowed.get(fn, 0xFF) >> mode & 1: # a mode it does not drive (fixture §1)
|
|
1613
1788
|
raise unsupported_fixed(m.tlv(_UNA["channel"], struct.pack("<H", ch)), m.tlv(index_tag, bytes([i])))
|
|
1614
1789
|
for i, (ch, mode) in enumerate(pairs):
|
|
1790
|
+
self._refuse_disabled([ch], m.tlv(index_tag, bytes([i]))) # disabled: cause 5 (probe.config §1)
|
|
1615
1791
|
if ch not in mine: # the position as the gpio's TLV (fixture §1)
|
|
1616
1792
|
raise unavailable(channel=ch, extra=m.tlv(index_tag, bytes([i])))
|
|
1617
1793
|
for ch, mode in pairs:
|
|
@@ -1623,6 +1799,7 @@ class Endpoint:
|
|
|
1623
1799
|
chans = [t.take("H") for _ in range(n)]
|
|
1624
1800
|
_, ignored = t.tail()
|
|
1625
1801
|
for i, ch in enumerate(chans):
|
|
1802
|
+
self._refuse_disabled([ch], m.tlv(_GPIO.tlv["unavailable_payload"]["index"], bytes([i])))
|
|
1626
1803
|
if ch not in mine:
|
|
1627
1804
|
raise unavailable(channel=ch, extra=m.tlv(_GPIO.tlv["unavailable_payload"]["index"], bytes([i])))
|
|
1628
1805
|
levels = []
|
|
@@ -1872,6 +2049,10 @@ class Endpoint:
|
|
|
1872
2049
|
places = [(s.wire_fn, s.pair) for s in slots.values()]
|
|
1873
2050
|
if len(set(places)) != len(places):
|
|
1874
2051
|
raise Reject(m.MALFORMED) # two slots on one place: the settings contradict
|
|
2052
|
+
disabled = {key for tag, key in new if tag == ITEM["disable"]}
|
|
2053
|
+
idles = {key for tag, key in new if tag == ITEM["idle"]}
|
|
2054
|
+
if disabled & idles:
|
|
2055
|
+
raise Reject(m.MALFORMED) # idle and disable for one channel (probe.config §1)
|
|
1875
2056
|
for fn in self.pairs: # every wire (pin_roles wires have an empty list)
|
|
1876
2057
|
if sum(1 for s in slots.values() if s.wire_fn == fn and s.attach == SLOT_ATTACH["at_boot"]) > \
|
|
1877
2058
|
self.max_connections.get(fn, 1):
|
|
@@ -1891,6 +2072,9 @@ class Endpoint:
|
|
|
1891
2072
|
elif tag == ITEM["idle"]:
|
|
1892
2073
|
if len(value) != 3 or value[2] > 2:
|
|
1893
2074
|
raise Reject(m.MALFORMED)
|
|
2075
|
+
elif tag == ITEM["disable"] and key not in self._all_channels():
|
|
2076
|
+
# a channel the firmware does not declare: unsupported, as idle (probe.config §1)
|
|
2077
|
+
raise unsupported_fixed(m.tlv(_UNA["channel"], struct.pack("<H", key)))
|
|
1894
2078
|
elif tag == ITEM["label"]:
|
|
1895
2079
|
if len(value) < 3:
|
|
1896
2080
|
raise Reject(m.MALFORMED)
|
|
@@ -1911,13 +2095,16 @@ class Endpoint:
|
|
|
1911
2095
|
self.slots = slots # the pin check below sees the new slots
|
|
1912
2096
|
try:
|
|
1913
2097
|
self._check_plan(want)
|
|
2098
|
+
self._check_disabled(new, disabled, plans, want, slots)
|
|
1914
2099
|
except Reject:
|
|
1915
2100
|
self.slots = {k: self._parse_slot(v) for (t, k), v in self.config.items() if t == ITEM["slot"]}
|
|
1916
2101
|
raise
|
|
1917
2102
|
# accepted: make it current
|
|
1918
|
-
for
|
|
2103
|
+
old_idle = {key: v for (tag, key), v in self.config.items() if tag == ITEM["idle"]}
|
|
2104
|
+
repark = (self.disabled - disabled) | {key for key in idles | set(old_idle) if new.get((ITEM["idle"], key)) != old_idle.get(key)}
|
|
2105
|
+
self.config = new # the disable items first: a dropped plan's pins
|
|
2106
|
+
for fn in old_plan_fns - set(plans): # are not parked when the same set disables them
|
|
1919
2107
|
self._drop_plan(fn)
|
|
1920
|
-
self.config = new
|
|
1921
2108
|
for fn, assigned in plans.items():
|
|
1922
2109
|
self.plan = {a for a in self.plan if a[0] != fn} | set(assigned)
|
|
1923
2110
|
self.plan_from_config.add(fn)
|
|
@@ -1937,12 +2124,40 @@ class Endpoint:
|
|
|
1937
2124
|
self.flows.pop((port, key), None)
|
|
1938
2125
|
self.mixed_out.pop(port, None)
|
|
1939
2126
|
self.binds = binds
|
|
2127
|
+
if not boot:
|
|
2128
|
+
self._park(repark) # an idle item set, or a channel enabled again: now
|
|
1940
2129
|
for n, s in slots.items():
|
|
1941
2130
|
if s.attach == SLOT_ATTACH["at_boot"] and (boot or changed_slots is None or n in changed_slots):
|
|
1942
2131
|
self.slot_rt[n].evicted = False
|
|
1943
2132
|
self._auto_attach(n)
|
|
1944
2133
|
self._refresh()
|
|
1945
2134
|
|
|
2135
|
+
def _check_disabled(self, new: dict, disabled: set[int], plans: dict, want: list, slots: dict) -> None:
|
|
2136
|
+
"""probe.config §1 disable: a channel in use now (a plan, a connection, a slot this set keeps) cannot be disabled
|
|
2137
|
+
(unavailable cause 1); an item naming a disabled channel (a plan, a slot's pins) is cause 5 with the channel."""
|
|
2138
|
+
for ch in sorted(disabled - self.disabled):
|
|
2139
|
+
for fn, _, c in sorted(self.plan):
|
|
2140
|
+
if c != ch:
|
|
2141
|
+
continue
|
|
2142
|
+
if fn in self.plan_from_config:
|
|
2143
|
+
if new.get((ITEM["plan"], fn)) == self.config.get((ITEM["plan"], fn)):
|
|
2144
|
+
raise unavailable("pin_in_use", ch, fn, "settings_plan")
|
|
2145
|
+
elif fn not in plans: # a session's plan this set does not replace
|
|
2146
|
+
raise unavailable("pin_in_use", ch, fn, "plan")
|
|
2147
|
+
conn = next((c for c in self.conns.values() if ch in c.pair), None)
|
|
2148
|
+
if conn is not None:
|
|
2149
|
+
raise unavailable("pin_in_use", ch, conn.fn, "connection")
|
|
2150
|
+
for (tag, key), value in self.config.items():
|
|
2151
|
+
if tag == ITEM["slot"] and new.get((tag, key)) == value and ch in self._parse_slot(value).pair:
|
|
2152
|
+
raise unavailable("pin_in_use", ch, holder_kind="slot")
|
|
2153
|
+
self._refuse_disabled_in(disabled, [c for _, _, c in want] + [p for s in slots.values() for p in s.pair])
|
|
2154
|
+
|
|
2155
|
+
@staticmethod
|
|
2156
|
+
def _refuse_disabled_in(disabled: set[int], channels: list[int]) -> None:
|
|
2157
|
+
for ch in channels:
|
|
2158
|
+
if ch != 0xFFFF and ch in disabled:
|
|
2159
|
+
raise unavailable("held_by_settings", ch, holder_kind="disabled")
|
|
2160
|
+
|
|
1946
2161
|
def _parse_slot(self, v: bytes) -> Slot:
|
|
1947
2162
|
"""probe.config §1.1, refused in core §4.3's order: the form (malformed), then what this probe lacks
|
|
1948
2163
|
(unsupported), then unknown fns."""
|
|
@@ -2093,8 +2308,9 @@ class Endpoint:
|
|
|
2093
2308
|
self.flows[(port, key)] = Flow(sid, 0)
|
|
2094
2309
|
|
|
2095
2310
|
def tick(self) -> None:
|
|
2096
|
-
"""Time passes: the lease, at-boot retries, mixed lines closed by quiet, the captures."""
|
|
2311
|
+
"""Time passes: the lease, at-boot retries, mixed lines closed by quiet, the captures, port_speed's timers."""
|
|
2097
2312
|
self._lapse()
|
|
2313
|
+
self._speed_tick()
|
|
2098
2314
|
now = self.now()
|
|
2099
2315
|
for fn, cap in self.captures.items():
|
|
2100
2316
|
self._events(fn, cap.tick(now))
|
|
@@ -2229,7 +2445,10 @@ class Endpoint:
|
|
|
2229
2445
|
s.written += data
|
|
2230
2446
|
|
|
2231
2447
|
def _session_over(self) -> None:
|
|
2232
|
-
"""The session ended (end, lapse, force): the ports it held resume from its last host reset (or now)
|
|
2448
|
+
"""The session ended (end, lapse, force): the ports it held resume from its last host reset (or now); a port
|
|
2449
|
+
off its boot speed goes back after the answer (core §3.5)."""
|
|
2450
|
+
if self.speed_state != "base":
|
|
2451
|
+
self.speed_pending = ("revert",)
|
|
2233
2452
|
for port in self.held_ports:
|
|
2234
2453
|
b = self.binds.get(port)
|
|
2235
2454
|
if b is None:
|
oep_client/fake.py
CHANGED
|
@@ -118,6 +118,7 @@ _CAPD = reg.FIXTURE_LOGIC.tlv["describe"]
|
|
|
118
118
|
_ANAD = reg.FIXTURE_ANALOG.tlv["describe"]
|
|
119
119
|
_GRPD = reg.FIXTURE_CAPTURE_GROUP.tlv["describe"]
|
|
120
120
|
CORE_CHIP = reg.CORE.tlv["describe"]["chip"]
|
|
121
|
+
CORE_PORT_SPEED = reg.CORE.tlv["describe"]["port_speed"] # the optional port_speed is on (core §3.5)
|
|
121
122
|
|
|
122
123
|
|
|
123
124
|
def _analog_decl(frontends: list[tuple[int, int, int, int]], max_samples: int) -> tuple[bytes, ...]:
|
|
@@ -245,13 +246,13 @@ def p4_x035() -> FakeProbe:
|
|
|
245
246
|
|
|
246
247
|
def esp32_v003() -> FakeProbe:
|
|
247
248
|
"""A small probe with 64-byte frames over a 115200 bps UART bridge (its only transport, serial port 0):
|
|
248
|
-
classic ESP32 on a CH32V003 (SWIO) jig."""
|
|
249
|
+
classic ESP32 on a CH32V003 (SWIO) jig. port_speed on (core §3.5), as the reference classic ESP32 firmware."""
|
|
249
250
|
reserved = [0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 15, 16]
|
|
250
251
|
wired = [4, 5, 13, 14, 17, 18, 19, 21, 22, 25, 26, 27, 32, 33]
|
|
251
252
|
return FakeProbe("esp32-v003", 64, [
|
|
252
253
|
_core("3.0.0", "esp32", "0070070d9394", 40, reserved, f"{NS}.esp32-v003",
|
|
253
254
|
{16: "SWIO", 23: "NRST", 22: "DUT TX", 21: "DUT RX"},
|
|
254
|
-
_transports([(TRANSPORT["uart_bridge"], 0xFF)])),
|
|
255
|
+
_transports([(TRANSPORT["uart_bridge"], 0xFF)]) + (catalog.u8(CORE_PORT_SPEED, 1),)),
|
|
255
256
|
Offered(1, 0, "oep.wire.swio", (catalog.channel_group(1, [(1, 16)]), catalog.role_channels(3, [23]),
|
|
256
257
|
catalog.u8(IMPLEMENTATION, 1), catalog.u8(MAX_CONNECTIONS, 1))),
|
|
257
258
|
Offered(2, 0, "oep.target.riscv-dm", (catalog.u32(FEATURES, 0b0111), catalog.u8(IMPLEMENTATION, 1),
|