oep-client-python 0.0.7__tar.gz → 0.0.8__tar.gz
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_python-0.0.7 → oep_client_python-0.0.8}/CHANGELOG.md +4 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/PKG-INFO +1 -1
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/pyproject.toml +1 -1
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/__init__.py +1 -1
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/endpoint.py +65 -14
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/fake.py +23 -2
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/registry.py +4 -4
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/riscv.py +20 -7
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_fake_spec.py +42 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/.gitignore +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/LICENSE +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/README.ja.md +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/README.md +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/__main__.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/arm.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/capture.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/catalog.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/ch32_flash.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/cobs.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/config.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/console.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/core.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/decode.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/dump.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/esp32_targets.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/fake_serial.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/fake_serve.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/fixture.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/frames.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/hid_stream.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/host.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/interfaces.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/link.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/message.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/names.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/rp2350.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/target.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/uiapduino.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/src/oep_client/usb_stream.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_capabilities.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_capture.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_cobs.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_config.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_esp32_targets.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_flash_console.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_hid_stream.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_interfaces.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_link_host.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_link_serial.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_session.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_target_parts.py +0 -0
- {oep_client_python-0.0.7 → oep_client_python-0.0.8}/tests/test_usb_stream.py +0 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.8
|
|
6
|
+
- (EN) Wires whose pins the host chooses (oep-if-debug §1, role_channels): the fake takes any allowed free pair, skips the pairs whose pins something holds in a count-0 scan, holds a live connection's pins against plans, and has a profile for it (`rp2350-pins`). `Wire.scan()` goes through the whole list (at most 255 pairs a request; count 0 continues with the new skip TLV until tried = 0).
|
|
7
|
+
- (JA) host がピンを選ぶ wire(oep-if-debug §1、role_channels): 偽の probe は、許された空いている組ならどれでも受け、count = 0 の scan では何かが持っているピンの組を飛ばし、生きている接続のピンを plan から守る。そのための profile(`rp2350-pins`)を足した。`Wire.scan()` は並びを最後まで回る(1 回 255 組まで。count = 0 は新しい skip の TLV で tried = 0 まで続ける)。
|
|
8
|
+
|
|
5
9
|
## 0.0.7
|
|
6
10
|
- (EN) `oep dump` names the wires' reset role (role 3) and no longer lists riscv-dm's clobbers (gone: the probe puts the registers back itself, oep-if-debug §4.5).
|
|
7
11
|
- (JA) `oep dump` は wire の reset の役(role 3)を名前で出し、riscv-dm の clobbers を出さなくなった(無くなった: probe が自分でレジスタを戻す、oep-if-debug §4.5)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a fake probe
|
|
5
5
|
Project-URL: Homepage, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
6
6
|
Project-URL: Repository, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oep-client-python"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.8"
|
|
8
8
|
description = "Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a fake probe"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -254,15 +254,26 @@ class Endpoint:
|
|
|
254
254
|
self.pairs: dict[int, list[tuple[int, int]]] = {} # wire fn -> allowed (swdio, swclk), declared order
|
|
255
255
|
self.max_connections: dict[int, int] = {}
|
|
256
256
|
self.reset_channels: dict[int, set[int]] = {} # wire fn -> channels attach_under_reset may take (role 3)
|
|
257
|
+
self.pin_roles: dict[int, dict[int, set[int]]] = {} # wire fn -> role -> candidates (role_channels wires)
|
|
257
258
|
for fn, name in self.names.items():
|
|
258
259
|
if name in WIRES:
|
|
259
260
|
self.pairs[fn] = [self._group_pair(name, t) for t in self.static[fn] if t[0] == catalog.CHANNEL_GROUP]
|
|
261
|
+
roles: dict[int, set[int]] = {}
|
|
262
|
+
for t in self.static[fn]:
|
|
263
|
+
if t[0] == catalog.ROLE_CHANNELS and t[2] in (1, 2):
|
|
264
|
+
roles.setdefault(t[2], set()).update(
|
|
265
|
+
catalog.bitmap_to_channels(struct.unpack_from("<H", t, 3)[0], t[5:2 + t[1]]))
|
|
266
|
+
if roles:
|
|
267
|
+
self.pin_roles[fn] = roles
|
|
260
268
|
self.reset_channels[fn] = {
|
|
261
269
|
c for t in self.static[fn] if t[0] == catalog.ROLE_CHANNELS and t[2] == PIN_ROLE_RESET
|
|
262
270
|
for c in catalog.bitmap_to_channels(struct.unpack_from("<H", t, 3)[0], t[5:2 + t[1]])}
|
|
263
271
|
self.max_connections[fn] = next((t[2] for t in self.static[fn] if t[0] == fake.MAX_CONNECTIONS), 1)
|
|
264
272
|
self.targets: dict[tuple[int, tuple[int, int]], FakeTarget] = {
|
|
265
273
|
(fn, p): FakeTarget() for fn in sorted(self.pairs) for p in self.pairs[fn]}
|
|
274
|
+
for fn in sorted(self.pin_roles): # any pair: one target, on the first pair
|
|
275
|
+
first = self._role_pairs(fn)[0]
|
|
276
|
+
self.targets[(fn, first)] = FakeTarget()
|
|
266
277
|
self.target = next(iter(self.targets.values()), FakeTarget())
|
|
267
278
|
self.mechanisms = set()
|
|
268
279
|
for fn, name in self.names.items():
|
|
@@ -329,6 +340,38 @@ class Endpoint:
|
|
|
329
340
|
def target_id(self, value: int | None) -> None:
|
|
330
341
|
self.target.target_id = value
|
|
331
342
|
|
|
343
|
+
def _role_pairs(self, fn: int) -> list[tuple[int, int]]:
|
|
344
|
+
"""A role_channels wire's pairs in the count = 0 order (oep-if-debug §1): swdio ascending, then swclk."""
|
|
345
|
+
roles = self.pin_roles[fn]
|
|
346
|
+
if self.names[fn] == "oep.wire.swio":
|
|
347
|
+
return [(d, 0xFFFF) for d in sorted(roles.get(1, ()))]
|
|
348
|
+
return [(d, c) for d in sorted(roles.get(1, ())) for c in sorted(roles.get(2, ())) if d != c]
|
|
349
|
+
|
|
350
|
+
def _allowed_pairs(self, fn: int) -> list[tuple[int, int]]:
|
|
351
|
+
return self._role_pairs(fn) if fn in self.pin_roles else self.pairs.get(fn, [])
|
|
352
|
+
|
|
353
|
+
def _allows(self, fn: int, pair: tuple[int, int]) -> bool:
|
|
354
|
+
if fn in self.pin_roles:
|
|
355
|
+
roles = self.pin_roles[fn]
|
|
356
|
+
swio = self.names[fn] == "oep.wire.swio"
|
|
357
|
+
return pair[0] in roles.get(1, ()) and (pair[1] == 0xFFFF if swio else
|
|
358
|
+
pair[1] in roles.get(2, ()) and pair[1] != pair[0])
|
|
359
|
+
return pair in self.pairs.get(fn, [])
|
|
360
|
+
|
|
361
|
+
def _held(self, fn: int | None = None, pair: tuple[int, int] | None = None) -> set[int]:
|
|
362
|
+
"""Channels something holds (core §8.1): the plan, the slots' pairs, the live connections' pairs - except
|
|
363
|
+
wire `fn`'s own connection on `pair` (attaching there again, or scanning through it)."""
|
|
364
|
+
held = {a[2] for a in self.plan}
|
|
365
|
+
held |= {p for s in self.slots.values() for p in s.pair if p != 0xFFFF and not (s.wire_fn == fn and s.pair == pair)}
|
|
366
|
+
held |= {p for c in self.conns.values() for p in c.pair if p != 0xFFFF and not (c.fn == fn and c.pair == pair)}
|
|
367
|
+
return held
|
|
368
|
+
|
|
369
|
+
def _target(self, fn: int, pair: tuple[int, int]) -> FakeTarget:
|
|
370
|
+
tg = self.targets.get((fn, pair))
|
|
371
|
+
if tg is None: # a pair nothing is wired to
|
|
372
|
+
tg = self.targets[(fn, pair)] = FakeTarget(present=False)
|
|
373
|
+
return tg
|
|
374
|
+
|
|
332
375
|
@staticmethod
|
|
333
376
|
def _group_pair(name: str, t: bytes) -> tuple[int, int]:
|
|
334
377
|
roles = {t[3 + 3 * i]: struct.unpack_from("<H", t, 4 + 3 * i)[0] for i in range((t[1] - 1) // 3)}
|
|
@@ -603,6 +646,7 @@ class Endpoint:
|
|
|
603
646
|
named = {fn for fn, _, _ in got}
|
|
604
647
|
kept = {a for a in self.plan if a[0] not in named}
|
|
605
648
|
slot_pins = {p for s in self.slots.values() for p in s.pair if p != 0xFFFF}
|
|
649
|
+
slot_pins |= {p for c in self.conns.values() for p in c.pair if p != 0xFFFF} # a live connection's pins too
|
|
606
650
|
for fn, role, ch in got:
|
|
607
651
|
roles = {"oep.fixture.gpio": {1}, "oep.fixture.uart": {1, 2}}.get(self.names.get(fn, ""), set())
|
|
608
652
|
if role not in roles or any(k[2] == ch for k in kept) or ch in slot_pins:
|
|
@@ -646,17 +690,24 @@ class Endpoint:
|
|
|
646
690
|
|
|
647
691
|
# ---- oep.wire.rvswd / swio ------------------------------------------------------------------
|
|
648
692
|
def _wire(self, fn: int, op: int, t: Take) -> tuple[int, int, bytes]:
|
|
649
|
-
allowed = self.pairs.get(fn, [])
|
|
650
693
|
if op == 0x01: # scan: count(u8) pairs -> tried count found...
|
|
651
694
|
count = t.take("B")
|
|
652
695
|
pairs = [t.take("HH") for _ in range(count)]
|
|
653
|
-
t.tail()
|
|
654
|
-
if
|
|
655
|
-
raise Reject(m.
|
|
656
|
-
|
|
696
|
+
got, _ = t.tail({0x01})
|
|
697
|
+
if 0x01 in got and (count or len(got[0x01]) != 2):
|
|
698
|
+
raise Reject(m.MALFORMED) # skip goes with count 0 only
|
|
699
|
+
live = [c.pair for c in self.conns.values() if c.fn == fn]
|
|
700
|
+
full = len(live) >= self.max_connections.get(fn, 1) # every seat taken: the live pairs only
|
|
701
|
+
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):
|
|
702
|
+
raise Reject(m.UNAVAILABLE) # not allowed, held (§8.1), or no seat to try it on
|
|
703
|
+
if not pairs: # the count-0 list, from `skip` on (oep-if-debug §1)
|
|
704
|
+
skip = struct.unpack("<H", got[0x01])[0] if 0x01 in got else 0
|
|
705
|
+
pairs = [p for p in self._allowed_pairs(fn)
|
|
706
|
+
if not set(p) & self._held(fn, p) and (not full or p in live)][skip:]
|
|
707
|
+
pairs = pairs[:255] # tried is a u8
|
|
657
708
|
found = []
|
|
658
709
|
for p in pairs:
|
|
659
|
-
tg = self.
|
|
710
|
+
tg = self._target(fn, p)
|
|
660
711
|
if tg.present: # a live connection's pair: read over it, no restart
|
|
661
712
|
found.append(struct.pack("<BHHI", 1, *p, tg.dmstatus()))
|
|
662
713
|
return m.COMPLETED, m.SUCCESS, struct.pack("<BB", len(pairs), len(found)) + b"".join(found)
|
|
@@ -675,7 +726,7 @@ class Endpoint:
|
|
|
675
726
|
raise Reject(m.MALFORMED)
|
|
676
727
|
idle_clock = got[0x04][0] if 0x04 in got else 0
|
|
677
728
|
pair = self._pick_pair(fn, got)
|
|
678
|
-
tg = self.
|
|
729
|
+
tg = self._target(fn, pair)
|
|
679
730
|
speed = min(4_000_000, struct.unpack("<I", got[0x01])[0]) if 0x01 in got else 4_000_000
|
|
680
731
|
cid = self._conn_at(fn, pair)
|
|
681
732
|
flags = 0
|
|
@@ -729,14 +780,14 @@ class Endpoint:
|
|
|
729
780
|
return m.REJECTED, m.UNKNOWN_OPERATION, b""
|
|
730
781
|
|
|
731
782
|
def _pick_pair(self, fn: int, got: dict[int, bytes]) -> tuple[int, int]:
|
|
732
|
-
allowed = self.pairs.get(fn, [])
|
|
733
783
|
if 0x03 in got:
|
|
734
784
|
if len(got[0x03]) != 4:
|
|
735
785
|
raise Reject(m.MALFORMED)
|
|
736
786
|
pair = struct.unpack("<HH", got[0x03])
|
|
737
|
-
if pair
|
|
738
|
-
raise Reject(m.UNAVAILABLE) #
|
|
787
|
+
if not self._allows(fn, pair) or set(pair) & self._held(fn, pair):
|
|
788
|
+
raise Reject(m.UNAVAILABLE) # not allowed here, or its pins are held (§8.1)
|
|
739
789
|
return pair
|
|
790
|
+
allowed = self._allowed_pairs(fn)
|
|
740
791
|
if len(allowed) != 1:
|
|
741
792
|
raise Reject(m.UNAVAILABLE) # the host chooses among several
|
|
742
793
|
return allowed[0]
|
|
@@ -779,7 +830,7 @@ class Endpoint:
|
|
|
779
830
|
|
|
780
831
|
def _target_of(self, cid: int) -> FakeTarget:
|
|
781
832
|
c = self.conns[cid]
|
|
782
|
-
return self.
|
|
833
|
+
return self._target(c.fn, c.pair)
|
|
783
834
|
|
|
784
835
|
# ---- oep.target.riscv-dm --------------------------------------------------------------------
|
|
785
836
|
@staticmethod
|
|
@@ -1181,7 +1232,7 @@ class Endpoint:
|
|
|
1181
1232
|
places = [(s.wire_fn, s.pair) for s in slots.values()]
|
|
1182
1233
|
if len(set(places)) != len(places):
|
|
1183
1234
|
raise Reject(m.UNAVAILABLE)
|
|
1184
|
-
for fn in self.pairs:
|
|
1235
|
+
for fn in self.pairs: # every wire (pin_roles wires have an empty list)
|
|
1185
1236
|
if sum(1 for s in slots.values() if s.wire_fn == fn and s.attach == SLOT_ATTACH["at_boot"]) > \
|
|
1186
1237
|
self.max_connections.get(fn, 1):
|
|
1187
1238
|
raise Reject(m.UNAVAILABLE)
|
|
@@ -1244,7 +1295,7 @@ class Endpoint:
|
|
|
1244
1295
|
raise Reject(m.MALFORMED)
|
|
1245
1296
|
if not SLOT_NAME.fullmatch(name.decode("ascii", "replace")):
|
|
1246
1297
|
raise Reject(m.MALFORMED)
|
|
1247
|
-
if self.names.get(wire_fn) not in WIRES or
|
|
1298
|
+
if self.names.get(wire_fn) not in WIRES or not self._allows(wire_fn, (swdio, swclk)):
|
|
1248
1299
|
raise Reject(m.UNAVAILABLE)
|
|
1249
1300
|
if mech not in self.mechanisms:
|
|
1250
1301
|
raise Reject(m.UNSUPPORTED)
|
|
@@ -1301,7 +1352,7 @@ class Endpoint:
|
|
|
1301
1352
|
s = self.slots[n]
|
|
1302
1353
|
rt = self.slot_rt[n]
|
|
1303
1354
|
rt.last_try_ms = self.now()
|
|
1304
|
-
tg = self.
|
|
1355
|
+
tg = self._target(s.wire_fn, s.pair)
|
|
1305
1356
|
cid = self._conn_at(s.wire_fn, s.pair)
|
|
1306
1357
|
if cid is None:
|
|
1307
1358
|
if not tg.present:
|
|
@@ -139,7 +139,8 @@ def _core(firmware: str, model: str, unit_id: bytes, channels: int, reserved: li
|
|
|
139
139
|
return Offered(0, 0, "oep.core", (
|
|
140
140
|
catalog.text(CORE_FIRMWARE, firmware), catalog.text(CORE_MODEL, model), catalog.tlv(CORE_UNIT_ID, unit_id),
|
|
141
141
|
catalog.u16(CORE_CHANNELS, channels), catalog.tlv(CORE_RESERVED, struct.pack("<H", base) + bits),
|
|
142
|
-
catalog.text(CORE_PROFILE, profile)) + tuple(_label(c, n) for c, n in labels.items())
|
|
142
|
+
) + ((catalog.text(CORE_PROFILE, profile),) if profile else ()) + tuple(_label(c, n) for c, n in labels.items())
|
|
143
|
+
+ extra)
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
def p4_x035() -> FakeProbe:
|
|
@@ -230,4 +231,24 @@ def p4_bench() -> FakeProbe:
|
|
|
230
231
|
])
|
|
231
232
|
|
|
232
233
|
|
|
233
|
-
|
|
234
|
+
def rp2350_pins() -> FakeProbe:
|
|
235
|
+
"""A board firmware whose wire takes its pins from the host (oep-if-debug §1, role_channels): any two of GP0-GP29
|
|
236
|
+
but GP19 (the Pro Micro RP2350's PSRAM CS) are an RVSWD pair, a reset line or fixture pins. USB CDC (serial port 0).
|
|
237
|
+
The fake target answers on (0, 1), the first pair."""
|
|
238
|
+
reserved = [19]
|
|
239
|
+
pins = [p for p in range(30) if p not in reserved]
|
|
240
|
+
return FakeProbe("rp2350-pins", 1024, [
|
|
241
|
+
_core("3.0.0", "sparkfun-promicro-rp2350", bytes.fromhex("e66138935f2b1f2c"), 30, reserved, "",
|
|
242
|
+
{}, _transports([(TRANSPORT["usb_cdc"], 0)])),
|
|
243
|
+
Offered(1, 1, "oep.wire.rvswd", _roles({1: pins, 2: pins, 3: pins}) + (
|
|
244
|
+
catalog.u32(MAX_CLOCK_HZ, 5_000_000), catalog.u8(IMPLEMENTATION, 1), catalog.u8(MAX_CONNECTIONS, 1))),
|
|
245
|
+
Offered(2, 1, "oep.target.riscv-dm", (catalog.u32(FEATURES, 0b1111), catalog.u8(IMPLEMENTATION, 1),
|
|
246
|
+
catalog.u16(MAX_LENGTH, 1000))),
|
|
247
|
+
Offered(3, 1, "oep.target.console", (catalog.tlv(MECHANISMS, bytes([0, 1, 2])),)),
|
|
248
|
+
Offered(4, 2, "oep.fixture.gpio", _roles({1: pins})),
|
|
249
|
+
Offered(5, 3, "oep.fixture.uart", _roles({1: pins, 2: pins}) + (
|
|
250
|
+
catalog.u32(MAX_CLOCK_HZ, 3_000_000), catalog.u8(IMPLEMENTATION, 2))),
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
PROFILES = {"p4-x035": p4_x035, "esp32-v003": esp32_v003, "p4-bench": p4_bench, "rp2350-pins": rp2350_pins}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from types import SimpleNamespace as _NS
|
|
4
4
|
|
|
5
|
-
REGISTRY_HASH = "
|
|
5
|
+
REGISTRY_HASH = "08b7d1656bf59269"
|
|
6
6
|
SCHEMA = 1
|
|
7
7
|
PROTOCOL_REVISION = 1
|
|
8
8
|
CONFIRM_REQUEST_MAGIC = 'OEP?'
|
|
@@ -25,13 +25,13 @@ CORE = _NS(name="oep.core", revision=1, op={"confirm": 0x01, "list": 0x02, "desc
|
|
|
25
25
|
closed_tail={0x40}, tlv={"plan_apply": {"role_assignment": 0x90}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "reserved": 0x44, "profile": 0x45, "label": 0x46, "resets_on_open": 0x47, "transport": 0x49, "oep_pid": 0x4A, "plan_roles": 0x4B}}, event={"heartbeat": 0x01}, enum={"transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}}, own={})
|
|
26
26
|
INTERFACES["oep.core"] = CORE
|
|
27
27
|
WIRE_RVSWD = _NS(name="oep.wire.rvswd", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "attach_under_reset": 0x04, "connections": 0x05}, lock_free={0x05},
|
|
28
|
-
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "swclk": 0x02, "reset": 0x03}, "idle_clock": {"high": 0x00, "low": 0x01}}, own={})
|
|
28
|
+
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "scan": {"skip": 0x01}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "swclk": 0x02, "reset": 0x03}, "idle_clock": {"high": 0x00, "low": 0x01}}, own={})
|
|
29
29
|
INTERFACES["oep.wire.rvswd"] = WIRE_RVSWD
|
|
30
30
|
WIRE_SWIO = _NS(name="oep.wire.swio", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "attach_under_reset": 0x04, "connections": 0x05}, lock_free={0x05},
|
|
31
|
-
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "reset": 0x03}}, own={})
|
|
31
|
+
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "scan": {"skip": 0x01}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "reset": 0x03}}, own={})
|
|
32
32
|
INTERFACES["oep.wire.swio"] = WIRE_SWIO
|
|
33
33
|
WIRE_SWD = _NS(name="oep.wire.swd", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "connections": 0x05}, lock_free={0x05},
|
|
34
|
-
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "targetsel": 0x02, "pins": 0x03}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "pin_role": {"swdio": 0x01, "swclk": 0x02}}, own={})
|
|
34
|
+
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "scan": {"skip": 0x01}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "targetsel": 0x02, "pins": 0x03}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "pin_role": {"swdio": 0x01, "swclk": 0x02}}, own={})
|
|
35
35
|
INTERFACES["oep.wire.swd"] = WIRE_SWD
|
|
36
36
|
TARGET_RISCV_DM = _NS(name="oep.target.riscv-dm", revision=1, op={"dmi": 0x01, "halt": 0x02, "resume": 0x03, "reset": 0x04, "read_block": 0x05, "write_block": 0x06, "run": 0x07, "step": 0x08}, lock_free={},
|
|
37
37
|
closed_tail={}, tlv={"describe": {}, "reset": {"method": 0x01}}, event={}, enum={"dmi_step": {"write": 0x01, "read": 0x02, "poll_reads": 0x03, "wait_us": 0x04, "poll_us": 0x05}, "reset_mode": {"run": 0x00, "run_verified": 0x01, "halt_at_reset": 0x02}, "reset_method": {"probe_default": 0x00, "ndmreset": 0x01, "system_reset": 0x02}}, own={})
|
|
@@ -76,23 +76,36 @@ class WireBase(Interface):
|
|
|
76
76
|
TAG_MAX_SPEED = 0x01
|
|
77
77
|
TAG_PINS = 0x03 # swdio(u16) swclk(u16, 0xFFFF on one wire), critical (oep-if-debug §1)
|
|
78
78
|
|
|
79
|
+
TAG_SKIP = 0x01 # scan, count 0 only: pairs of the count-0 list to skip (oep-if-debug §1)
|
|
80
|
+
|
|
79
81
|
def scan(self, pairs: list[tuple[int, int]] | None = None) -> list[Found]:
|
|
80
|
-
"""Try `pairs` of (swdio, swclk); None = every pair the probe allows (describe's
|
|
81
|
-
role_channels). A pair the probe does not allow
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
"""Try `pairs` of (swdio, swclk); None = every pair the probe allows and nothing holds (describe's
|
|
83
|
+
channel_group / role_channels, oep-if-debug §1). A pair the probe does not allow, or one whose pins something
|
|
84
|
+
holds, refuses the whole scan (rejected unavailable). The probe tries at most 255 pairs a request and stops early
|
|
85
|
+
when its answer would not fit one frame; this goes on until every pair is tried (count 0: with skip until
|
|
86
|
+
tried = 0)."""
|
|
84
87
|
out = []
|
|
88
|
+
pairs = list(pairs or [])
|
|
89
|
+
skip = 0
|
|
85
90
|
while True:
|
|
86
|
-
|
|
91
|
+
chunk = pairs[:255]
|
|
92
|
+
body = bytes([len(chunk)]) + b"".join(struct.pack("<HH", d, c) for d, c in chunk)
|
|
93
|
+
if not pairs and skip:
|
|
94
|
+
body += m.tlv(self.TAG_SKIP, struct.pack("<H", skip))
|
|
87
95
|
rd = m.Reader(self._call(self.SCAN, body).payload)
|
|
88
96
|
tried, count = rd.take("BB")
|
|
89
97
|
for _ in range(count):
|
|
90
98
|
kind, dio, clk, status = rd.take("BHHI")
|
|
91
99
|
out.append(Found(kind, (dio, clk), status))
|
|
92
100
|
rd.tail()
|
|
93
|
-
if
|
|
101
|
+
if tried == 0:
|
|
94
102
|
return out
|
|
95
|
-
|
|
103
|
+
if pairs:
|
|
104
|
+
pairs = pairs[tried:]
|
|
105
|
+
if not pairs:
|
|
106
|
+
return out
|
|
107
|
+
else:
|
|
108
|
+
skip += tried
|
|
96
109
|
|
|
97
110
|
def detach(self, conn: int) -> None:
|
|
98
111
|
self._call(self.DETACH, struct.pack("<H", conn))
|
|
@@ -431,3 +431,45 @@ def test_idle_clock_is_rvswd_s_and_a_slot_carries_the_line_settings():
|
|
|
431
431
|
assert hv.raw(9, 0x02, slot_item(0, 1, v003.pairs[1][0], idle=1)).detail == m.MALFORMED # swio has no idle_clock
|
|
432
432
|
r = hv.raw(1, 0x02, b"\x01" + idle_low)
|
|
433
433
|
assert r.resolution == m.REJECTED and r.detail == m.UNSUPPORTED # an unknown critical TLV on swio
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def pins_probe():
|
|
437
|
+
ep = endpoint.Endpoint(fake.rp2350_pins(), Clock())
|
|
438
|
+
h = Host(ep)
|
|
439
|
+
h.open()
|
|
440
|
+
return ep, h
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_a_wire_takes_any_free_pair_the_host_names():
|
|
444
|
+
"""oep-if-debug §1: role_channels pairs; count 0 in swdio / swclk order; a live connection holds its pins."""
|
|
445
|
+
ep, h = pins_probe()
|
|
446
|
+
pins = lambda d, c: m.tlv(0x03, struct.pack("<HH", d, c), critical=True)
|
|
447
|
+
r = m.Reader(h.ok(1, 0x01, b"\x00")) # count 0: the first 255 free pairs
|
|
448
|
+
tried, count = r.take("BB")
|
|
449
|
+
assert tried == 255 and count == 1 and r.take("BHHI")[1:3] == (0, 1)
|
|
450
|
+
skip = lambda n: m.tlv(0x01, struct.pack("<H", n))
|
|
451
|
+
assert m.Reader(h.ok(1, 0x01, b"\x00" + skip(29 * 28 - 10))).take("B") == 10 # the last ten
|
|
452
|
+
assert m.Reader(h.ok(1, 0x01, b"\x00" + skip(29 * 28))).take("B") == 0 # the end
|
|
453
|
+
assert h.raw(1, 0x01, b"\x01" + struct.pack("<HH", 0, 1) + skip(1)).detail == m.MALFORMED
|
|
454
|
+
conn = struct.unpack_from("<H", h.ok(1, 0x02, b"\x01" + pins(0, 1)))[0]
|
|
455
|
+
assert h.raw(1, 0x02, b"\x01" + pins(1, 2)).detail == m.UNAVAILABLE # GP1 is the live connection's
|
|
456
|
+
assert h.raw(0, m.OP_PLAN_APPLY, m.tlv(0x90, struct.pack("<HBH", 4, 1, 0))).detail == m.UNAVAILABLE
|
|
457
|
+
assert m.Reader(h.ok(1, 0x01, b"\x00")).take("B") == 1 # its one seat is taken: the live pair only
|
|
458
|
+
assert h.raw(1, 0x01, b"\x01" + struct.pack("<HH", 2, 3)).detail == m.UNAVAILABLE
|
|
459
|
+
h.ok(1, 0x03, struct.pack("<H", conn) + m.tlv(0x01, b"")) # detach (force): the pins go back
|
|
460
|
+
h.ok(0, m.OP_PLAN_APPLY, m.tlv(0x90, struct.pack("<HBH", 4, 1, 5)))
|
|
461
|
+
assert h.raw(1, 0x01, b"\x01" + struct.pack("<HH", 5, 6)).detail == m.UNAVAILABLE # GP5 is the plan's
|
|
462
|
+
assert h.raw(1, 0x02, b"\x01" + pins(3, 3)).detail == m.UNAVAILABLE # one channel twice
|
|
463
|
+
assert h.raw(1, 0x02, b"\x01" + pins(19, 3)).detail == m.UNAVAILABLE # not offered (PSRAM CS)
|
|
464
|
+
r = h.raw(1, 0x02, b"\x01" + pins(7, 8)) # a free pair nothing answers on
|
|
465
|
+
assert r.resolution == m.COMPLETED and not r.succeeded
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def test_the_client_scan_walks_the_whole_count_0_list():
|
|
469
|
+
from oep_client import host as hh, target
|
|
470
|
+
ep = endpoint.Endpoint(fake.rp2350_pins(), Clock())
|
|
471
|
+
hst = hh.Host(lambda b: ep.handle(b, 0))
|
|
472
|
+
hst.open(3000)
|
|
473
|
+
ep._target(1, (28, 29)).present = True # the last pair of the list
|
|
474
|
+
found = target.Wire(hst).scan()
|
|
475
|
+
assert [f.pins for f in found] == [(0, 1), (28, 29)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|