oep-client-python 0.0.33__py3-none-any.whl → 0.0.34__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 CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.0.33"
5
+ __version__ = "0.0.34"
oep_client/__main__.py CHANGED
@@ -359,6 +359,12 @@ def _config_parser(sub) -> None:
359
359
  rest.add_argument("channel", type=int, help="the port's SWCLK channel")
360
360
  rest.add_argument("mode", choices=sorted(config.REST_MODE))
361
361
  rest.add_argument("--save", action="store_true")
362
+ hold = cs.add_parser("wire-hold", help="whether a debug wire's lines stay held at their rest after a close "
363
+ "(on, the default) or go free at once (off; debug §2)")
364
+ hold.add_argument("probe")
365
+ hold.add_argument("channel", type=int, help="the port's SWDIO channel")
366
+ hold.add_argument("mode", choices=sorted(config.HOLD_MODE))
367
+ hold.add_argument("--save", action="store_true")
362
368
  dis = cs.add_parser("disable", help="channels the probe never drives or sets up (not on this board)")
363
369
  dis.add_argument("probe")
364
370
  dis.add_argument("channels", type=int, nargs="+")
@@ -441,6 +447,8 @@ def _config(args) -> int:
441
447
  _change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
442
448
  elif args.action == "idle":
443
449
  _change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode)], args.save)
450
+ elif args.action == "wire-hold":
451
+ _change(hst, cfg, [config.WireHold(channel=args.channel, mode=args.mode)], args.save)
444
452
  elif args.action == "rvswd-rest":
445
453
  _change(hst, cfg, [config.RvswdRest(channel=args.channel, mode=args.mode)], args.save)
446
454
  elif args.action == "disable":
oep_client/config.py CHANGED
@@ -37,6 +37,7 @@ ITEM = {k.replace("-", "_"): i + 1 for i, k in enumerate(NAMES)}
37
37
  DESCRIBE = _CFG.tlv["describe"]
38
38
  IDLE = {k.replace("_", "-"): v for k, v in _CFG.enum["idle_mode"].items()}
39
39
  REST_MODE = dict(_CFG.enum["rvswd_rest_mode"]) # rvswd-rest: auto / high / low (debug §3.1)
40
+ HOLD_MODE = dict(_CFG.enum["wire_hold_mode"]) # wire-hold: off / on (debug §2)
40
41
  STORAGE_STATE = {v: k for k, v in _CFG.enum["storage_state"].items()}
41
42
  UNREADABLE = {v: k for k, v in _CFG.enum["storage_unreadable"].items()}
42
43
  FORM = _CFG.enum["setting_form"]
@@ -162,6 +163,26 @@ class RvswdRest:
162
163
  return bytes([REST_MODE[self.mode]])
163
164
 
164
165
 
166
+ @dataclass
167
+ class WireHold:
168
+ """Whether a debug wire's lines on the port whose SWDIO is `channel` stay held at their rest after its connection
169
+ closes (debug §2): on - also with no item - or off (back to the free state at once)."""
170
+ channel: int
171
+ mode: str = "on"
172
+ NAME = "wire-hold"
173
+
174
+ def key(self) -> int:
175
+ return self.channel
176
+
177
+ def value(self) -> bytes:
178
+ return struct.pack("<H", self.key()) + self.data()
179
+
180
+ def data(self) -> bytes:
181
+ if self.mode not in HOLD_MODE:
182
+ raise ValueError(f"wire-hold mode {self.mode!r}: one of {', '.join(HOLD_MODE)}")
183
+ return bytes([HOLD_MODE[self.mode]])
184
+
185
+
165
186
  @dataclass(kw_only=True)
166
187
  class ConsolePassthrough:
167
188
  """Serial port `port` carries the target's debug console - read by the probe itself over `wire_fn` on (swdio, swclk)
@@ -315,6 +336,8 @@ def decode(name: str, key: int, d: bytes, setting: Setting | None = None, netifs
315
336
  try:
316
337
  if name == "label":
317
338
  return Label(channel=key, text=d.decode("utf-8", "replace"))
339
+ if name == "wire-hold" and len(d) == 1 and d[0] in HOLD_MODE.values():
340
+ return WireHold(channel=key, mode={v: k for k, v in HOLD_MODE.items()}[d[0]])
318
341
  if name == "rvswd-rest" and len(d) == 1 and d[0] in REST_MODE.values():
319
342
  return RvswdRest(channel=key, mode={v: k for k, v in REST_MODE.items()}[d[0]])
320
343
  if name == "idle" and len(d) == 1:
oep_client/endpoint.py CHANGED
@@ -117,7 +117,8 @@ RESET_MODE = _RV.enum["reset_mode"]
117
117
  READ_FROM = reg.COMMON.enum["read_from"] # position, oldest, now, last_mark (common §1.2)
118
118
  STEP_LEFT = _RV.tlv["step_answer"]["step_left"] # step's answer: the hart could not be halted again (§4.2)
119
119
  ITEM = {k.replace("-", "_"): k for k in cfgmod.NAMES} # the standard items by name (probe.config §1, core §7.7)
120
- CHANNEL_ITEMS = (ITEM["label"], ITEM["idle"], ITEM["disable"], ITEM["rvswd_rest"]) # keyed by a channel (probe.config §1)
120
+ CHANNEL_ITEMS = (ITEM["label"], ITEM["idle"], ITEM["disable"], ITEM["rvswd_rest"], ITEM["wire_hold"]) # keyed by a channel
121
+ HOLD_MODE = cfgmod.HOLD_MODE
121
122
  REST_MODE = cfgmod.REST_MODE
122
123
  IDLE_MODE = _CFG.enum["idle_mode"]
123
124
  CFG_DESCRIBE = cfgmod.DESCRIBE
@@ -327,6 +328,7 @@ class VirtualTarget:
327
328
  havereset: bool = True
328
329
  resume_misses: int = 0 # resumes that do not take (status state, dpc unchanged)
329
330
  present: bool = True # something answers on this pin pair (scan, attach)
331
+ needs_drive: bool = False # loses its halt once the probe stops driving the line (a CH32L103)
330
332
  target_id: int | None = None # the dmi_7f target_id attach reports (None: none)
331
333
  # run(pc, regs) -> (stopped, dpc, elapsed_us); default: halts 0x10 past the start
332
334
  run_hook: Callable | None = None
@@ -554,7 +556,7 @@ class Connection:
554
556
 
555
557
 
556
558
  ITEM_SIZES = {ITEM["idle"]: 1, ITEM["disable"]: 0, ITEM["passthrough"]: 9, # the data of a fixed length (probe.config §1)
557
- ITEM["console_passthrough"]: 13, ITEM["ipv4"]: 14, ITEM["rvswd_rest"]: 1}
559
+ ITEM["console_passthrough"]: 13, ITEM["ipv4"]: 14, ITEM["rvswd_rest"]: 1, ITEM["wire_hold"]: 1}
558
560
  PASSTHROUGHS = (ITEM["passthrough"], ITEM["console_passthrough"])
559
561
 
560
562
 
@@ -784,6 +786,7 @@ class Endpoint:
784
786
  self.session_ports: set[int] = set() # the transports the lock holder's requests came on (probe.config §1.1)
785
787
  self.passthrough_out: dict[int, bytearray] = {} # a serial port's pass-through bytes not sent yet
786
788
  self.resident: dict[int, bool] = {} # console_passthrough's own connection per port: attached (config §1.2)
789
+ self.holds: dict[tuple[int, tuple[int, int]], int] = {} # (wire fn, pair) -> rest: lines held after a close (debug §2)
787
790
  self.last: int | None = None # S: the id that holds or last held the lock (the resend table's, §5.2)
788
791
  self.owner: bytes | None = None # the holder's owner, while the lock is held (core §6.4)
789
792
  self.lease_ms = self.lease_default_ms
@@ -1561,7 +1564,7 @@ class Endpoint:
1561
1564
  connections closed (an unknown `cid`: none)."""
1562
1565
  gone = [c for c in (list(self.conns) if cid is None else [cid]) if c in self.conns]
1563
1566
  for c in gone:
1564
- self._close_conn(c)
1567
+ self._close_conn(c, lost=True)
1565
1568
  return gone
1566
1569
 
1567
1570
  # ---- core -----------------------------------------------------------------------------------
@@ -2334,6 +2337,11 @@ class Endpoint:
2334
2337
  raise wrong_kind() # another wire's connection
2335
2338
  self._close_conn(cid)
2336
2339
  return m.COMPLETED, m.SUCCESS, b""
2340
+ if op == O["holds"]: # lock-free, paged by first (debug §2.2)
2341
+ first = t.take("B")
2342
+ t.tail()
2343
+ rows = [struct.pack("<HHB", *pair, rest) for pair, rest in self.held_ports(fn)]
2344
+ return m.COMPLETED, m.SUCCESS, self._paged(rows, first)
2337
2345
  if op == O["connections"]: # lock-free, paged by first (debug §2.1)
2338
2346
  first = t.take("B")
2339
2347
  t.tail()
@@ -2409,14 +2417,45 @@ class Endpoint:
2409
2417
  self.conns[cid] = Connection(fn, pair, self._order, speed, tid)
2410
2418
  return cid
2411
2419
 
2412
- def _close_conn(self, cid: int) -> None:
2413
- """The connection goes, its console streams first (common §2); its pins go back to their idle state."""
2420
+ def _close_conn(self, cid: int, lost: bool = False) -> None:
2421
+ """The connection goes, its console streams first (common §2). Its lines stay held at its rest unless the line
2422
+ was lost, the port's wire-hold is off or a pin has an idle / disable item (debug §2); else to their idle state,
2423
+ and a target that needs the drive loses its halt."""
2414
2424
  for sid in [k for k, st in self.streams.items() if st.conn == cid]:
2415
2425
  self._close_stream(sid)
2416
2426
  gone = self.conns.pop(cid, None)
2417
2427
  self.resources.pop(cid, None)
2418
- if gone is not None:
2419
- self._park(gone.pair)
2428
+ if gone is None:
2429
+ return
2430
+ if not lost and self._may_hold(gone.pair):
2431
+ self.holds[(gone.fn, gone.pair)] = gone.idle_clock if self.names[gone.fn] == "oep.wire.rvswd" else 0
2432
+ return
2433
+ self._park(gone.pair)
2434
+ tg = self.targets.get((gone.fn, gone.pair))
2435
+ if tg is not None and tg.needs_drive:
2436
+ tg.halted = False # its debug module dropped the link: the hart runs
2437
+
2438
+ def _may_hold(self, pair: tuple[int, int]) -> bool:
2439
+ """debug §2: wire-hold on (no item: on) for the port's SWDIO, and no idle / disable item on its pins."""
2440
+ mode = self.config.get((ITEM["wire_hold"], pair[0]), bytes([HOLD_MODE["on"]]))[0]
2441
+ pins = [p for p in pair if p != 0xFFFF]
2442
+ return (mode == HOLD_MODE["on"] and not any((ITEM["idle"], p) in self.config for p in pins)
2443
+ and not any(p in self.disabled for p in pins))
2444
+
2445
+ def held_ports(self, fn: int) -> list[tuple[tuple[int, int], int]]:
2446
+ """The ports wire `fn` holds now (debug §2.2): a hold ends once its pins are taken (a plan, a connection), its
2447
+ wire-hold goes off or an idle / disable item lands on them; an ended hold's pins go to their free state."""
2448
+ busy = {a[2] for a in self.plan if not self._listens(a[0])} | {p for c in self.conns.values() for p in c.pair}
2449
+ for key in list(self.holds):
2450
+ hfn, pair = key
2451
+ if any(p in busy for p in pair if p != 0xFFFF) or not self._may_hold(pair):
2452
+ del self.holds[key]
2453
+ if not any(p in busy for p in pair if p != 0xFFFF):
2454
+ self._park(pair)
2455
+ tg = self.targets.get(key)
2456
+ if tg is not None and tg.needs_drive:
2457
+ tg.halted = False
2458
+ return [(pair, rest) for (hfn, pair), rest in self.holds.items() if hfn == fn]
2420
2459
 
2421
2460
  def _cleanup_failed(self, cid: int, done: int, payload: bytes) -> tuple[int, int, bytes]:
2422
2461
  """A block op whose restore of DATA0 / DATA1 failed: partial when done > 0 (all words included), else failed,
@@ -3301,6 +3340,8 @@ class Endpoint:
3301
3340
  return True
3302
3341
  if name == ITEM["rvswd_rest"] and d[0] not in REST_MODE.values():
3303
3342
  return True # a mode the definition leaves unused (§1)
3343
+ if name == ITEM["wire_hold"] and d[0] not in HOLD_MODE.values():
3344
+ return True
3304
3345
  return False
3305
3346
 
3306
3347
  def _shown(self, name: str, d: bytes) -> bytes:
oep_client/registry.py CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  from types import SimpleNamespace as _NS
5
5
 
6
- REGISTRY_HASH = "66b19f04290391f3"
6
+ REGISTRY_HASH = "32b9f952338089a5"
7
7
  SCHEMA = 1
8
8
  PROTOCOL_REVISION = 1
9
9
  CONFIRM_REQUEST_MAGIC = 'OEP?'
@@ -41,15 +41,15 @@ PROBE_LINK = _NS(name="oep.probe.link", revision=1, target=None, op={"source": 0
41
41
  tlv={}, event={}, enum={"port_speed_step": {"try": 0x00, "commit": 0x01, "revert": 0x02}, "port_state": {"boot": 0x00, "trying": 0x01, "committed": 0x02, "recovery_pending": 0x03}}, own={},
42
42
  line_names={}, names={})
43
43
  INTERFACES["oep.probe.link"] = PROBE_LINK
44
- WIRE_RVSWD = _NS(name="oep.wire.rvswd", revision=1, target="RISC-V targets whose debug module is reached over the two-wire RVSWD line", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13}, lock_free={0x13},
44
+ WIRE_RVSWD = _NS(name="oep.wire.rvswd", revision=1, target="RISC-V targets whose debug module is reached over the two-wire RVSWD line", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13, "holds": 0x14}, lock_free={0x13, 0x14},
45
45
  tlv={"describe": {"max_connections": 0x40}, "scan": {"max_speed": 0x01, "idle_clock": 0x03}, "attach": {"idle_clock": 0x03, "reset": 0x04}, "attach_answer": {"target_id": 0x01, "dpc": 0x02, "idle_clock": 0x03}, "connections_answer": {"idle_clock": 0x02}, "unavailable_payload": {"index": 0x40}, "unsupported_payload": {"index": 0x40}}, event={}, enum={"scan_kind": {"riscv_dm": 0x01}, "attach_flags": {"havereset_acked": 0x01, "halted": 0x08}, "features": {"attach_halt": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "swclk": 0x02, "reset": 0x03}, "idle_clock": {"high": 0x00, "low": 0x01}, "target_id_family": {"shift": 0x14}, "idle_low_family": {"ch32l103": 0x103}}, own={},
46
46
  line_names={}, names={})
47
47
  INTERFACES["oep.wire.rvswd"] = WIRE_RVSWD
48
- WIRE_SWIO = _NS(name="oep.wire.swio", revision=1, target="RISC-V targets whose debug module is reached over the one-wire SWIO line", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13}, lock_free={0x13},
48
+ WIRE_SWIO = _NS(name="oep.wire.swio", revision=1, target="RISC-V targets whose debug module is reached over the one-wire SWIO line", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13, "holds": 0x14}, lock_free={0x13, 0x14},
49
49
  tlv={"describe": {"max_connections": 0x40}, "scan": {"max_speed": 0x01}, "attach": {"reset": 0x04}, "attach_answer": {"target_id": 0x01, "dpc": 0x02}, "unavailable_payload": {"index": 0x40}, "unsupported_payload": {"index": 0x40}}, event={}, enum={"scan_kind": {"riscv_dm": 0x01}, "attach_flags": {"havereset_acked": 0x01, "halted": 0x08}, "features": {"attach_halt": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "reset": 0x03}}, own={},
50
50
  line_names={}, names={})
51
51
  INTERFACES["oep.wire.swio"] = WIRE_SWIO
52
- WIRE_SWD = _NS(name="oep.wire.swd", revision=1, target="ARM targets with an SWD debug port", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13}, lock_free={0x13},
52
+ WIRE_SWD = _NS(name="oep.wire.swd", revision=1, target="ARM targets with an SWD debug port", op={"scan": 0x10, "attach": 0x11, "detach": 0x12, "connections": 0x13, "holds": 0x14}, lock_free={0x13, 0x14},
53
53
  tlv={"describe": {"max_connections": 0x40}, "scan": {"max_speed": 0x01, "targetsel": 0x02}, "attach": {"targetsel": 0x02, "reset": 0x04}, "attach_answer": {"target_id": 0x01}, "connections_answer": {"selected_target": 0x01}, "unavailable_payload": {"index": 0x40}, "unsupported_payload": {"index": 0x40}}, event={}, enum={"scan_kind": {"arm_adi": 0x02}, "attach_flags": {"dormant_woken": 0x04}, "features": {}, "attach_method": {"run": 0x00}, "pin_role": {"swdio": 0x01, "swclk": 0x02, "reset": 0x03}}, own={},
54
54
  line_names={}, names={})
55
55
  INTERFACES["oep.wire.swd"] = WIRE_SWD
@@ -94,6 +94,6 @@ FIXTURE_CAPTURE_GROUP = _NS(name="oep.fixture.capture-group", revision=1, target
94
94
  line_names={}, names={})
95
95
  INTERFACES["oep.fixture.capture-group"] = FIXTURE_CAPTURE_GROUP
96
96
  PROBE_CONFIG = _NS(name="oep.probe.config", revision=1, target=None, op={"get": 0x10, "set": 0x11, "save": 0x12, "erase": 0x13, "unset": 0x14, "state": 0x15, "network": 0x16}, lock_free={0x10, 0x15, 0x16},
97
- tlv={"describe": {"storage": 0x40, "setting": 0x41, "netif": 0x42}}, event={}, enum={"idle_mode": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "output_low": 0x03, "output_high": 0x04}, "rvswd_rest_mode": {"auto": 0x00, "high": 0x01, "low": 0x02}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}, "setting_form": {"named": 0x00, "bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_unreadable": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}}, own={},
98
- line_names={'swdio': 'the debug data line (2 wires: rvswd, swd) (Host only)', 'swclk': 'the debug clock line (2 wires: rvswd, swd) (Host only)', 'swio': 'the one debug wire (swio) (Host only)', 'rx': 'the fixture UART receive, wired to the target TX (Host only)', 'tx': 'the fixture UART transmit, wired to the target RX (Host only)', 'nrst': 'the target reset line (Host only)', 'power_hi': 'target power on when high (Host only)', 'power_lo': 'target power on when low (Host only)'}, names={'setting': {'label': 'key channel; text 1-32 bytes', 'idle': 'key channel; mode(u8)', 'disable': 'key channel; no data', 'passthrough': 'key port; fn(u16) rx(u16) baud(u32) format(u8)', 'console-passthrough': 'key port; wire_fn(u16) swdio(u16) swclk(u16) max_speed(u32) console_fn(u16) mechanism(u8)', 'wifi': 'key entry; ssid_len(u8) ssid pass_len(u8) passphrase; passphrase write-only', 'ipv4': 'key netif; mode(u8) address(4) prefix(u8) gateway(4) dns(4)', 'rvswd-rest': "key channel (the port's SWCLK); mode(u8): rvswd_rest_mode"}, 'netif': {'wifi': 'Wi-Fi station', 'ethernet': 'wired Ethernet'}})
97
+ tlv={"describe": {"storage": 0x40, "setting": 0x41, "netif": 0x42}}, event={}, enum={"idle_mode": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "output_low": 0x03, "output_high": 0x04}, "rvswd_rest_mode": {"auto": 0x00, "high": 0x01, "low": 0x02}, "wire_hold_mode": {"off": 0x00, "on": 0x01}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}, "setting_form": {"named": 0x00, "bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_unreadable": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}}, own={},
98
+ line_names={'swdio': 'the debug data line (2 wires: rvswd, swd) (Host only)', 'swclk': 'the debug clock line (2 wires: rvswd, swd) (Host only)', 'swio': 'the one debug wire (swio) (Host only)', 'rx': 'the fixture UART receive, wired to the target TX (Host only)', 'tx': 'the fixture UART transmit, wired to the target RX (Host only)', 'nrst': 'the target reset line (Host only)', 'power_hi': 'target power on when high (Host only)', 'power_lo': 'target power on when low (Host only)'}, names={'setting': {'label': 'key channel; text 1-32 bytes', 'idle': 'key channel; mode(u8)', 'disable': 'key channel; no data', 'passthrough': 'key port; fn(u16) rx(u16) baud(u32) format(u8)', 'console-passthrough': 'key port; wire_fn(u16) swdio(u16) swclk(u16) max_speed(u32) console_fn(u16) mechanism(u8)', 'wifi': 'key entry; ssid_len(u8) ssid pass_len(u8) passphrase; passphrase write-only', 'ipv4': 'key netif; mode(u8) address(4) prefix(u8) gateway(4) dns(4)', 'rvswd-rest': "key channel (the port's SWCLK); mode(u8): rvswd_rest_mode", 'wire-hold': "key channel (the port's SWDIO); mode(u8): wire_hold_mode"}, 'netif': {'wifi': 'Wi-Fi station', 'ethernet': 'wired Ethernet'}})
99
99
  INTERFACES["oep.probe.config"] = PROBE_CONFIG
oep_client/riscv.py CHANGED
@@ -188,7 +188,7 @@ class WireBase(Interface):
188
188
  """oep.wire.<link>: scan / attach / detach / connections (oep-if-debug §0-§2). The shared part; each link's attach
189
189
  takes its own arguments. A failed scan, attach or detach is completed failed with `status(u8) [TLV]` (common §3).
190
190
  attach names its pins in the fixed part (§1): `pins` None takes the wire's one candidate pair (candidate_pairs)."""
191
- SCAN, ATTACH, DETACH, CONNECTIONS = (_WIRE.op[k] for k in ("scan", "attach", "detach", "connections"))
191
+ SCAN, ATTACH, DETACH, CONNECTIONS, HOLDS = (_WIRE.op[k] for k in ("scan", "attach", "detach", "connections", "holds"))
192
192
  REVISION = 1
193
193
  TAG_RESET = _WIRE.tlv["attach"]["reset"] # channel(u16) hold_ms(u16), critical: attach under reset (§3)
194
194
  TAG_SCAN_MAX_SPEED = _WIRE.tlv["scan"]["max_speed"]
@@ -299,6 +299,19 @@ class WireBase(Interface):
299
299
  """Close the connection and its streams (oep-if-debug §2)."""
300
300
  self._call(self.DETACH, struct.pack("<I", conn))
301
301
 
302
+ def holds(self) -> list[tuple[tuple[int, int], int]]:
303
+ """The ports this wire holds at their rest after a close (oep-if-debug §2.2, lock-free; paged like connections):
304
+ [((swdio, swclk), rest)] - rest: rvswd's idle_clock value (0 high, 1 low), 0 on other wires. Not connections."""
305
+ out: list[tuple[tuple[int, int], int]] = []
306
+ while True:
307
+ rd = m.Reader(self._call(self.HOLDS, bytes([len(out)]), locked=False).payload)
308
+ more, count = rd.take("BB")
309
+ page = [((d, c), r) for d, c, r in (rd.take("HHB") for _ in range(count))]
310
+ rd.tail()
311
+ out += page
312
+ if not more or not count or len(out) > 0xFF:
313
+ return out
314
+
302
315
  def connections(self) -> list["ConnectionInfo"]:
303
316
  """The wire's live connections, in the order they were made (oep-if-debug §2.1, lock-free; paged: the request
304
317
  is first(u8), the answer more count entries [TLV], followed until more is 0). swd's selected_target TLVs
@@ -36,7 +36,7 @@ class Attachments(Dependencies):
36
36
  return bytes(page)
37
37
  def describe(self,fn):
38
38
  if fn==4:return (tlv(DC['ops'],pack_ops(set(COP.values()))),*(tlv(CD['mechanism'],name.encode()) for name in self.mechanisms))
39
- rows=[tlv(DC['ops'],pack_ops({WOP['attach'],WOP['detach'],WOP['connections']})),tlv(WT['describe']['max_connections'],b'\x02'),tlv(DC['min_clock_hz'],struct.pack('<I',1000))]
39
+ rows=[tlv(DC['ops'],pack_ops({WOP['attach'],WOP['detach'],WOP['connections'],WOP['holds']})),tlv(WT['describe']['max_connections'],b'\x02'),tlv(DC['min_clock_hz'],struct.pack('<I',1000))]
40
40
  for index,pair in enumerate(self.pairs[fn]):
41
41
  members=[(WE['pin_role']['swdio'],pair[0])] if fn==2 else [(WE['pin_role']['swdio'],pair[0]),(WE['pin_role']['swclk'],pair[1])]
42
42
  rows.append(tlv(DC['channel_group'],bytes((index,len(members)))+b''.join(struct.pack('<BH',*row) for row in members)))
@@ -79,6 +79,7 @@ class Attachments(Dependencies):
79
79
  def dispatch_request(self,fn,op,payload,tags):
80
80
  if fn<=3:
81
81
  if op==WOP['connections']:return self.connection_page(fn,payload[0])
82
+ if op==WOP['holds']:return b'\0\0'+extension() # no port held in this model (debug §2.2)
82
83
  if op==WOP['detach']:
83
84
  rid=struct.unpack('<I',payload)[0];self.resource(rid,'connection')
84
85
  if self.connections[rid]['fn']!=fn:raise Reject(R['unavailable'],cause('wrong_state'))
@@ -131,7 +132,7 @@ class AttachModel(DependencyModel):
131
132
  if len(request)>=6 and struct.unpack_from('<H',request,3)[0]==4:
132
133
  e.fixed={COP['open']:5,COP['read']:15,COP['marks']:8,COP['clear']:4,COP['mark']:5,COP['write']:6,COP['close']:4,COP['streams']:2};e.free={COP['read'],COP['marks'],COP['streams']}
133
134
  if request[5]==COP['write'] and len(request)>=16:e.fixed[COP['write']]=6+struct.unpack_from('<H',request,14)[0]
134
- else:e.fixed={WOP['attach']:9,WOP['detach']:4,WOP['connections']:1};e.free={WOP['connections']}
135
+ else:e.fixed={WOP['attach']:9,WOP['detach']:4,WOP['connections']:1,WOP['holds']:1};e.free={WOP['connections'],WOP['holds']}
135
136
  try:return self.ep.handle(request)
136
137
  finally:e.fixed,e.free=fixed,free
137
138
  def restart_fixture(self):
@@ -262,7 +262,7 @@ def _setting(name: str, keys: int, form: int = 0, max_len: int = 0, flags: int =
262
262
 
263
263
 
264
264
  def _config(fn: int, instance: int, storage: int = 4096, *, channels: int = 64, ports: int = 4, netifs=(), wifi_entries: int = 4,
265
- own=(), rvswd: bool = False) -> Offered:
265
+ own=(), rvswd: bool = False, hold: bool = True) -> Offered:
266
266
  """oep.probe.config's declarations (probe.config §4): the standard items label / idle / disable (keys: the
267
267
  channels), passthrough / console-passthrough (keys: the transports), with `netifs` (names) wifi / ipv4 and the
268
268
  network op; `own`: the probe's own items as (name, form, keys, max_len, flags) (§1.6). storage 0 = none - no storage
@@ -276,6 +276,8 @@ def _config(fn: int, instance: int, storage: int = 4096, *, channels: int = 64,
276
276
  settings.append(_setting("ipv4", len(netifs) + 1))
277
277
  if rvswd: # its automatic rest can be turned off per port (debug §3.1)
278
278
  settings.append(_setting("rvswd-rest", channels))
279
+ if hold: # a debug wire's hold after a close, per port (debug §2)
280
+ settings.append(_setting("wire-hold", channels))
279
281
  settings += [_setting(name, keys, form, max_len, flags) for name, form, keys, max_len, flags in own]
280
282
  return Offered(fn, instance, "oep.probe.config", ops_of("oep.probe.config", *drop)
281
283
  + ((catalog.u32(_CFG["storage"], storage),) if storage else ()) + tuple(settings)
@@ -24,7 +24,7 @@ class OepDeclarations(Resources):
24
24
 
25
25
  def describe(self, fn):
26
26
  if fn==4:return (tlv(DC['ops'],pack_ops(set(COP.values()))),tlv(CD['mechanism'],b'dmdata'))
27
- rows=[tlv(DC['ops'],pack_ops({WOP['attach'],WOP['detach'],WOP['connections']}))]
27
+ rows=[tlv(DC['ops'],pack_ops({WOP['attach'],WOP['detach'],WOP['connections'],WOP['holds']}))]
28
28
  if fn==1:rows.append(tlv(WT['describe']['max_connections'],b'\x02'))
29
29
  if fn==2:rows.append(tlv(DC['min_clock_hz'],struct.pack('<I',1000000)))
30
30
  role=WE['pin_role']
@@ -35,7 +35,7 @@ class OepDeclarations(Resources):
35
35
  return tuple(rows)
36
36
 
37
37
  def dispatch(self, fn, op, payload):
38
- if (fn!=4 and op==WOP['connections']) or (fn==4 and op==COP['streams']):return b'\0\0'+extension()
38
+ if (fn!=4 and op in (WOP['connections'],WOP['holds'])) or (fn==4 and op==COP['streams']):return b'\0\0'+extension()
39
39
  raise Reject(R['no_resource']) # declarations only: no simulated attach or target work
40
40
 
41
41
 
@@ -50,6 +50,6 @@ class OepDeclarationModel(ResourceModel):
50
50
  if len(request)>=6:
51
51
  fn=struct.unpack_from('<H',request,3)[0]
52
52
  if fn==4:e.fixed={COP['open']:5,COP['read']:15,COP['marks']:8,COP['clear']:4,COP['mark']:5,COP['write']:6,COP['close']:4,COP['streams']:2};e.free={COP['read'],COP['marks'],COP['streams']}
53
- else:e.fixed={WOP['attach']:9,WOP['detach']:4,WOP['connections']:1};e.free={WOP['connections']}
53
+ else:e.fixed={WOP['attach']:9,WOP['detach']:4,WOP['connections']:1,WOP['holds']:1};e.free={WOP['connections'],WOP['holds']}
54
54
  try:return super().handle(request)
55
55
  finally:e.fixed,e.free=fixed,free
@@ -8,7 +8,7 @@ from . import registry as _reg
8
8
  # Each interface's mandatory ops, by name (looked up in the registry: never literal numbers).
9
9
  _MANDATORY={
10
10
  'oep.probe.plan':('plan_apply','plan_release'),'oep.probe.restart':('restart',),'oep.probe.link':('source','sink'),
11
- **{w:('attach','detach','connections') for w in ('oep.wire.rvswd','oep.wire.swio','oep.wire.swd')},
11
+ **{w:('attach','detach','connections','holds') for w in ('oep.wire.rvswd','oep.wire.swio','oep.wire.swd')},
12
12
  'oep.target.riscv-dm':('dmi',),'oep.target.arm-adi':('transfer',),
13
13
  'oep.target.console':('open','read','close','streams'),
14
14
  'oep.fixture.gpio':('set','read'),'oep.fixture.uart':('configure','read','status'),
@@ -14,7 +14,7 @@ KIND = _reg.WIRE_RVSWD.enum["scan_kind"] | _reg.WIRE_SWD.enum["scan_kind"]
14
14
 
15
15
  class ScanOptions(WireOptions):
16
16
  def __init__(self,ep):super().__init__(ep);self.scan_limit=2;self.identities={};self.scan_live=set();self.scan_events=[]
17
- def describe(self,fn):return tuple(tlv(DC['ops'],pack_ops({WOP['scan'],WOP['attach'],WOP['detach'],WOP['connections']})) if fn<=3 and row[0]==DC['ops'] else row for row in super().describe(fn))
17
+ def describe(self,fn):return tuple(tlv(DC['ops'],pack_ops({WOP['scan'],WOP['attach'],WOP['detach'],WOP['connections'],WOP['holds']})) if fn<=3 and row[0]==DC['ops'] else row for row in super().describe(fn))
18
18
  def request_tags(self,fn,op,data):
19
19
  if fn>3 or op!=WOP['scan']:return super().request_tags(fn,op,data)
20
20
  known={SC['max_speed']:4}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.33
3
+ Version: 0.0.34
4
4
  Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a virtual bench
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
@@ -1,11 +1,11 @@
1
- oep_client/__init__.py,sha256=i55a89BxR1hu3ff6BPlfP_aEJn4skTGl0cJphRFGQdo,164
2
- oep_client/__main__.py,sha256=l7KIG3-55GovEmb9dboeA-gInoNBwCXziCERHbBQCyY,32873
1
+ oep_client/__init__.py,sha256=WV8YLSlZH0QElGgErRZN3FX_Ujze1gxUJA5gNUbc0M4,164
2
+ oep_client/__main__.py,sha256=7NYC9SZWd7M17YhOpjj15nu7_7-mEF_cQMfXJVkg53c,33449
3
3
  oep_client/arm.py,sha256=SszSLae8zErD4T-a1uQ6s6o3Lz7pe2SR8d3Qy-wi5rY,15633
4
4
  oep_client/capture.py,sha256=AFyJ-_DRb3ztYlvmFCDh9siNaKrwChOx8xvoNH6CKsI,48884
5
5
  oep_client/catalog.py,sha256=WkaksNn9cFvkTRnvtX4I9g1AvkFoldEiNQaKiyPQ7_E,10772
6
6
  oep_client/ch32_flash.py,sha256=lY_HKxf2foSfhNda91lcWefgRnY9ebTljsg4zJEj-Ks,12885
7
7
  oep_client/cobs.py,sha256=RbdPrqgzD3SukxOIXX9m2dZpVTud8xAYYEhm3rcW2nA,2513
8
- oep_client/config.py,sha256=OTePLd-X1QC7xOk36BmhqfF8M7WwBXaDvuz_jiBmrIM,29575
8
+ oep_client/config.py,sha256=1X66HIrT9WCvj7MjhPDET-jIrNyaQU1AM-wGq8BHpp8,30487
9
9
  oep_client/conformance.py,sha256=phKkP_vT_8b9MyQiJU0p7JoCnkrPbPdkolcEqaD61TI,62859
10
10
  oep_client/conformance_allocator.py,sha256=83xXiJ7c-6-M86615Kvq6yDD0sf0Z6lIOKDtbYcKeyM,6274
11
11
  oep_client/conformance_allocator_sample.py,sha256=GGv43xbDNSPjQ8_flNXe-QbkLOr-NCbPpWPOfbD_wH8,879
@@ -178,7 +178,7 @@ oep_client/core.py,sha256=VPtgaVe2K5-HJMi72DDbIpIirATAXjcJSKautKUFgOM,22677
178
178
  oep_client/decode.py,sha256=6pTFVBT0MLAKedZLVKyd5yT8hjrRw4fdDjnoDiE9XRs,2380
179
179
  oep_client/discovery.py,sha256=OS6c4Qa8N32zbOHmw75e1y6ff3SQrL0uLtyPi7BWKUA,20187
180
180
  oep_client/dump.py,sha256=31v4Hg27Het3cu3joCm6ttTWxQHSvvNryyGo8pwHXO4,12788
181
- oep_client/endpoint.py,sha256=DmKqB_uLHQnvUBuv5q_jcofD_9spbWjCJ1_1tieFJMo,226457
181
+ oep_client/endpoint.py,sha256=Fqo5nSsWoqRulnwVujMhpNn4Uux57oZiuLrTKx5kcbk,229246
182
182
  oep_client/fixture.py,sha256=GbAPXAk9GQ0qG6STL-rY4-EqXftmKfNKVI2xZZUiFzA,18108
183
183
  oep_client/frames.py,sha256=OrvoNugGB4ewfmILhwBiJfI9sQ09i-RzO47LESVbWXw,4800
184
184
  oep_client/hardware.py,sha256=RboxkRxkQJ3DbNJEXyyiOFYlLFKGxrlcSwHho8zFC1U,30256
@@ -192,8 +192,8 @@ oep_client/message.py,sha256=-WgnHcmnhJ6pozYvZP_FS8dr0ZMg2Wp9rTdRReGvogU,13116
192
192
  oep_client/names.py,sha256=ohrRvQNURraYyJ-BtjHRwxGt_GLq-t8IO0aOji9Dhe0,3531
193
193
  oep_client/pins.py,sha256=YVM0JXjCKcMMMWChtWsQoSKU4-OTOnDbZjo_mo_5hD0,32574
194
194
  oep_client/pytest_conformance.py,sha256=-bcJU2E1UE4xjQ7HZjaIgtp0l65cIibBTNFnaW2tews,2929
195
- oep_client/registry.py,sha256=eeUL7cquyunsaSQYofRNXH0y8sAFkX8sRtmwc7-GZjA,16688
196
- oep_client/riscv.py,sha256=J0HHHrI-xrIt46zob3RpmOqy9XV12-futGeU1SC-xjY,59201
195
+ oep_client/registry.py,sha256=8ynarYyjC54IuD45PgXAzxIkpEs9ly-QAX2uZWIk1qI,16869
196
+ oep_client/riscv.py,sha256=_98qwqnxUIj9vitWIz6iJfJKsT63FNsQH9eZrZnqB4g,59939
197
197
  oep_client/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
198
198
  oep_client/speed_record.py,sha256=6THUKbN7gQKpfIdUO2551P8mXiAFqbFDky72Xbki7L0,7229
199
199
  oep_client/targets.py,sha256=zFekSr76NVO7tzdgyQsLDHLWRvkgylfwvGNTz_hQGf8,5041
@@ -202,8 +202,8 @@ oep_client/usb_stream.py,sha256=pFbQexircX88eV8MZG7ECfUtRXCboBr-4-dtahvF6ro,1135
202
202
  oep_client/virtual_allocator_model.py,sha256=XBrbjG2Zupf0P28gjUhQVDw6UzO2jQXsYZ1E1qAIKc8,1570
203
203
  oep_client/virtual_analog_model.py,sha256=iV5pyJ1sy37D6VMy4qVmgzsmq4gLwEPMcdOGXPIin1Q,13872
204
204
  oep_client/virtual_arm_adi_model.py,sha256=xlqhjr_QCF6MNVOnVelTanlNWpmaCJ5uWXf6V9WkqUM,8121
205
- oep_client/virtual_attach_model.py,sha256=11UfEicbmTKcjps8JPzw99J59Dl_QGVbyyuFb0ixzso,10288
206
- oep_client/virtual_bench.py,sha256=WXLJICEhaTo5kapOujCcH-CEfIgtudlzanrUv_tVIss,35944
205
+ oep_client/virtual_attach_model.py,sha256=YhGYz4X2IcdBMsBb0vjkV2UWUst6tSVieZHUwQZ89gA,10433
206
+ oep_client/virtual_bench.py,sha256=6wJNW5odjS5UpeLfUTOIgKfWM_aXdPcqIrtTHGnZbFI,36137
207
207
  oep_client/virtual_bench_capture.py,sha256=hIIOUOqKcSxBnsCb9rcyzYFqSsBmuic7sAkF7zsBAIs,52208
208
208
  oep_client/virtual_bench_mdns.py,sha256=cnJNBKg5zTduXUcH-wqN_ZBDGl7DyL-tL3CBn2jro0E,13305
209
209
  oep_client/virtual_bench_serial.py,sha256=EvbdOYeGrM6CRti2b90I0PzpmmherfoJRaAg4v7Iq9Y,13205
@@ -238,12 +238,12 @@ oep_client/virtual_logic_model.py,sha256=_a4z_BLYWaoPS2o4NqWiNEElFBWI2pkCc0psf4h
238
238
  oep_client/virtual_mailbox_model.py,sha256=VNjwg-_E_djPwpys95RIWg26_DctS6uqo16Kg5i5ckA,2996
239
239
  oep_client/virtual_names.py,sha256=p72ME65R9Jy56xmVYnVCtapK3Id8iFpAdI5xLe2tNZ0,1449
240
240
  oep_client/virtual_notification_model.py,sha256=iUYcQidlUq2DJ6emd5VXwuPMRw2bOKuSa0A_fc2_OVM,2673
241
- oep_client/virtual_oep_declaration_model.py,sha256=Kdu5SSWOVnZX3np5RonFAKdCmN6InWEGs2-ynlAve4A,2893
241
+ oep_client/virtual_oep_declaration_model.py,sha256=LrhcsEAnG-hwyrXaio9owpOEZj6oC2gGK5HIl9kwwPk,2951
242
242
  oep_client/virtual_outcome_model.py,sha256=cmMiM9XqhqCZ523YShkWFDKAn6NPMM6hnWNxqBGPxVc,3704
243
243
  oep_client/virtual_pipeline_model.py,sha256=3XHOQAB1pm5wOxfVBtIK60L_N_d-fUpSgVX0S_5-V7E,4063
244
244
  oep_client/virtual_port_speed_execution_model.py,sha256=vgUSHHmMNFC1UL6s1S_0t9wJg_dDUYXTJ7NXqM5lPfg,1676
245
245
  oep_client/virtual_port_speed_model.py,sha256=1gI0tlN4ObTm3B1Ys1BsEJ02c6jYAqfIbaFFbVmn7cE,8680
246
- oep_client/virtual_registered_ops_model.py,sha256=F7yErR8MZjn1F0EgMo_m34Z8j-EMpTbeEE_DOjneCzk,2722
246
+ oep_client/virtual_registered_ops_model.py,sha256=MRYX08hhfsY1IH3MHzdWz9uEiTN5OQBTU08fuEWUWVc,2730
247
247
  oep_client/virtual_reset_model.py,sha256=mfIMxArHv7LRZjC9tjYkVJ0flYuZlOVrJChWmgiNh1Y,4303
248
248
  oep_client/virtual_resource_model.py,sha256=XTYxvgIz371EQJ4-vKs3arqSf4kV8nNYKgFjFmRc6dk,3151
249
249
  oep_client/virtual_restart_model.py,sha256=ZDHo4MCF1EuP1eo0utKqCZUOHTl6cIgFO8FOQqEqUGM,4651
@@ -252,7 +252,7 @@ oep_client/virtual_retry_model.py,sha256=2Vf7Seic2FX7Mwf3UR4UHBGJYHuqpKIaxgMmURy
252
252
  oep_client/virtual_route_model.py,sha256=k3bWMKakWzDNnoaqK80LKWK5RTU2q1eYhAK_OSlgOLY,7299
253
253
  oep_client/virtual_run_model.py,sha256=qYFb1vmhbJFnoM00Tq1Kj0x3T8GVlY5Nb4rsBxGG3Nw,6464
254
254
  oep_client/virtual_scan_model.py,sha256=9Q5h4hRfFdPzEKwjgIPFa0vl5OYax08AVa4JJDsnMeI,4330
255
- oep_client/virtual_scan_options_model.py,sha256=UviKXTQDC_KM3cd7PdiI5mvK0PexQqGMxUezwQRW9G8,6201
255
+ oep_client/virtual_scan_options_model.py,sha256=_aFpSL5wXzicZSflniaov5G3NABeTqstEE8yLmbnIk4,6214
256
256
  oep_client/virtual_selected_metadata_model.py,sha256=uKUy73sAqVHt1xPuY3LgjYbq-BMLZ_jnU70VYGfDo9g,934
257
257
  oep_client/virtual_spi_model.py,sha256=uKcA0bG1v4tMCm8IFOmlCzC9CN3d2MFPSUf4wP0jS88,10258
258
258
  oep_client/virtual_step_model.py,sha256=g-CfAloAktuf3oB9eR8wB291Pnh0leezaP1Drqrbr_Q,4200
@@ -261,8 +261,8 @@ oep_client/virtual_tlv_request_model.py,sha256=gp-eiN784NnloQBANG_4TnM6oUCmrj046
261
261
  oep_client/virtual_uart_model.py,sha256=Sd_MWLjl1-6_bemsCuI2at9zpVJRBehFQgZ6By97PdQ,9386
262
262
  oep_client/virtual_wire_options_model.py,sha256=1QNF16hRWmdFt0J7Zd1WKZdEc07vns6osMQLetMBlXw,9206
263
263
  oep_client/virtual_writer_lease_model.py,sha256=E1MAQsGuShqONVdIeLubun7Cop0URNUSk8wicPicbt4,1728
264
- oep_client_python-0.0.33.dist-info/METADATA,sha256=ruHISiMjzQOjtZv3t2Bcyo3LxM8292-_CCUMc0NUXLk,52711
265
- oep_client_python-0.0.33.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
266
- oep_client_python-0.0.33.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
267
- oep_client_python-0.0.33.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
268
- oep_client_python-0.0.33.dist-info/RECORD,,
264
+ oep_client_python-0.0.34.dist-info/METADATA,sha256=epM_SIcto4RoFOoLl3JP7ZRYwMi5lf-XBvU9n2Ufzi4,52711
265
+ oep_client_python-0.0.34.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
266
+ oep_client_python-0.0.34.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
267
+ oep_client_python-0.0.34.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
268
+ oep_client_python-0.0.34.dist-info/RECORD,,