oep-client-python 0.0.5__py3-none-any.whl → 0.0.7__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.5"
5
+ __version__ = "0.0.7"
oep_client/__main__.py CHANGED
@@ -64,6 +64,10 @@ def _config_parser(sub) -> None:
64
64
  slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
65
65
  slot.add_argument("--retry", type=int, default=0, help="at-boot: try again every N s while absent (0: never)")
66
66
  slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq")
67
+ slot.add_argument("--max-speed", type=int, default=0,
68
+ help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
69
+ slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
70
+ help="rvswd: SWCLK while the line rests (the target's: low on CH32L103 / V203)")
67
71
  slot.add_argument("--lock", help="MASK:VALUE (hex u32) the target_id (WCH DMI 0x7F) must match, e.g. ffffff0f:035e0600")
68
72
  slot.add_argument("--save", action="store_true", help="save after the change")
69
73
  bind = cs.add_parser("bind", help="what a serial port carries")
@@ -183,7 +187,7 @@ def _config(args) -> int:
183
187
  mask, _, value = args.lock.partition(":")
184
188
  lock = (1, struct.pack("<I", int(mask, 16)), struct.pack("<I", int(value, 16)))
185
189
  it = config.Slot(args.slot, fn, _pins(hst, fn, args.pins, args.wire), args.name, args.attach, args.retry,
186
- args.mechanism, lock)
190
+ args.mechanism, lock, args.max_speed, args.idle_clock)
187
191
  _change(hst, cfg, [it], args.save)
188
192
  elif args.action == "bind":
189
193
  slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
@@ -250,6 +254,7 @@ def _show(hst, cfg, as_json: bool) -> int:
250
254
  s = by_slot.get(it.slot)
251
255
  pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
252
256
  retry = f" retry {it.retry_s} s" if it.attach == "at-boot" else ""
257
+ retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
253
258
  lock = (f" lock {int.from_bytes(it.lock[1], 'little'):08x}:{int.from_bytes(it.lock[2], 'little'):08x}"
254
259
  if it.lock else "")
255
260
  live = ""
oep_client/capture.py CHANGED
@@ -347,18 +347,20 @@ class LogicCapture(Interface):
347
347
  return [(data[(i * c.width + bit0) >> 3] >> ((i * c.width + bit0) & 7)) & 1 for i in range(n)]
348
348
 
349
349
  def to_sr(self, path: str, data: bytes, samples: int, names: list[str] | None = None) -> None:
350
- """A sigrok session file: one byte per sample, bit k = channel k (so up to 8 channels here)."""
350
+ """A sigrok session file: bit k of each sample = channel k, one byte per sample up to 8 channels, two (little
351
+ endian, sigrok's unitsize 2) up to 16."""
351
352
  c = self.config
352
353
  n_ch = len(c.positions)
353
- if n_ch > 8:
354
- raise ValueError("to_sr writes one byte per sample; more than 8 channels need unitsize 2")
354
+ unit = 1 if n_ch <= 8 else 2
355
+ if n_ch > 16:
356
+ raise ValueError("to_sr writes up to 16 channels")
355
357
  names = names or [f"D{k}" for k in range(n_ch)]
356
358
  chans = [self.channel(data, k, samples) for k in range(n_ch)]
357
- out = bytes(sum(chans[k][i] << k for k in range(n_ch)) for i in range(samples))
359
+ out = b"".join(sum(chans[k][i] << k for k in range(n_ch)).to_bytes(unit, "little") for i in range(samples))
358
360
  rate = c.rate.numerator // c.rate.denominator
359
361
  meta = ["[global]", "sigrok version=0.5.2", "", "[device 1]", "capturefile=logic-1",
360
362
  f"total probes={n_ch}", f"samplerate={rate} Hz", "total analog=0"]
361
- meta += [f"probe{k + 1}={nm}" for k, nm in enumerate(names)] + ["unitsize=1", ""]
363
+ meta += [f"probe{k + 1}={nm}" for k, nm in enumerate(names)] + [f"unitsize={unit}", ""]
362
364
  with zipfile.ZipFile(path, "w") as z:
363
365
  z.writestr("version", "2")
364
366
  z.writestr("metadata", "\n".join(meta))
oep_client/config.py CHANGED
@@ -29,6 +29,7 @@ MECHANISM = {k: v for k, v in reg.TARGET_CONSOLE.enum["mechanism"].items()}
29
29
  SLOT_STATE = {v: k.replace("_", "-") for k, v in _CFG.enum["slot_state"].items()}
30
30
  BIND_FLOW = {v: k for k, v in _CFG.enum["bind_flow"].items()}
31
31
  IDLE = {k.replace("_", "-"): v for k, v in _CFG.enum["idle_mode"].items()}
32
+ IDLE_CLOCK = dict(reg.WIRE_RVSWD.enum["idle_clock"]) # a slot's idle_clock (oep-if-debug §3)
32
33
  STORAGE_STATE = {v: k for k, v in _CFG.enum["storage_state"].items()}
33
34
 
34
35
 
@@ -79,12 +80,15 @@ class Slot:
79
80
  retry_s: int = 0 # at-boot: try again every retry_s while the target is not there (0: never)
80
81
  mechanism: str = "dmseq" # sdi, dmdata, dmseq
81
82
  lock: tuple[int, bytes, bytes] | None = None
83
+ max_speed: int = 0 # the line's ceiling in Hz for the probe's own attach (0: none) - the target's
84
+ idle_clock: str = "high" # rvswd: SWCLK while the line rests, high / low - the target's (oep-if-debug §3)
82
85
  TAG = ITEM["slot"]
83
86
 
84
87
  def value(self) -> bytes:
85
88
  name = self.name.encode()
86
- v = struct.pack("<BHHHBHBB", self.slot, self.wire_fn, *self.pins, ATTACH[self.attach],
87
- self.retry_s if self.attach == "at-boot" else 0, MECHANISM[self.mechanism], len(name)) + name
89
+ v = struct.pack("<BHHHBHIBBB", self.slot, self.wire_fn, *self.pins, ATTACH[self.attach],
90
+ self.retry_s if self.attach == "at-boot" else 0, self.max_speed, IDLE_CLOCK[self.idle_clock],
91
+ MECHANISM[self.mechanism], len(name)) + name
88
92
  if self.lock is None:
89
93
  return v + b"\x00"
90
94
  scheme, mask, value = self.lock
@@ -127,15 +131,16 @@ def decode(tag: int, v: bytes):
127
131
  return Label(struct.unpack_from("<H", v)[0], v[2:].decode("utf-8", "replace"))
128
132
  if tag == ITEM["idle"] and len(v) == 3:
129
133
  return Idle(struct.unpack_from("<H", v)[0], _name(IDLE, v[2]))
130
- if tag == ITEM["slot"] and len(v) >= 13:
131
- n, wire_fn, swdio, swclk, attach, retry_s, mech, name_len = struct.unpack_from("<BHHHBHBB", v)
132
- name = v[12:12 + name_len].decode("ascii", "replace")
133
- rest = v[12 + name_len:]
134
+ if tag == ITEM["slot"] and len(v) >= 18:
135
+ n, wire_fn, swdio, swclk, attach, retry_s, max_speed, idle, mech, name_len = struct.unpack_from("<BHHHBHIBBB", v)
136
+ name = v[17:17 + name_len].decode("ascii", "replace")
137
+ rest = v[17 + name_len:]
134
138
  lock = None
135
139
  if rest and rest[0]:
136
140
  half = (len(rest) - 1) // 2
137
141
  lock = (rest[0], rest[1:1 + half], rest[1 + half:])
138
- return Slot(n, wire_fn, (swdio, swclk), name, _name(ATTACH, attach), retry_s, _name(MECHANISM, mech), lock)
142
+ return Slot(n, wire_fn, (swdio, swclk), name, _name(ATTACH, attach), retry_s, _name(MECHANISM, mech), lock,
143
+ max_speed, _name(IDLE_CLOCK, idle))
139
144
  if tag == ITEM["bind"] and len(v) >= 4:
140
145
  port, mode, selected, n = struct.unpack_from("<BBBB", v)
141
146
  streams = [(_name(STREAM, v[4 + 3 * k]), struct.unpack_from("<H", v, 5 + 3 * k)[0]) for k in range(n)]
oep_client/endpoint.py CHANGED
@@ -60,6 +60,7 @@ WIRES = ("oep.wire.rvswd", "oep.wire.swio")
60
60
  OWNER = reg.CORE.tlv["open"]["owner"]
61
61
  SLOT_NAME = re.compile(r"[a-z0-9_-]{1,32}")
62
62
  NO_SLOT, NEVER = 0xFF, 0xFFFFFFFF
63
+ PIN_ROLE_RESET = reg.WIRE_RVSWD.enum["pin_role"]["reset"] # the channels attach_under_reset may take
63
64
 
64
65
 
65
66
  class Reject(Exception):
@@ -180,6 +181,7 @@ class Connection:
180
181
  speed: int = 4_000_000
181
182
  tid: int | None = None
182
183
  users: set = field(default_factory=set) # "host" and/or ("slot", n)
184
+ idle_clock: int = 0 # rvswd: SWCLK while the line rests, 0 high / 1 low
183
185
 
184
186
 
185
187
  @dataclass(frozen=True)
@@ -189,6 +191,8 @@ class Slot:
189
191
  pair: tuple[int, int]
190
192
  attach: int
191
193
  retry_s: int
194
+ max_speed: int # 0: no ceiling (oep-if-debug §3: the target's, the host's to set)
195
+ idle_clock: int
192
196
  mechanism: int
193
197
  name: str
194
198
  lock: tuple[int, bytes, bytes] | None # (scheme, mask, value)
@@ -249,9 +253,13 @@ class Endpoint:
249
253
  self.serial_ports = {i for i, k in enumerate(self.transports) if k in fake.SERIAL_KINDS}
250
254
  self.pairs: dict[int, list[tuple[int, int]]] = {} # wire fn -> allowed (swdio, swclk), declared order
251
255
  self.max_connections: dict[int, int] = {}
256
+ self.reset_channels: dict[int, set[int]] = {} # wire fn -> channels attach_under_reset may take (role 3)
252
257
  for fn, name in self.names.items():
253
258
  if name in WIRES:
254
259
  self.pairs[fn] = [self._group_pair(name, t) for t in self.static[fn] if t[0] == catalog.CHANNEL_GROUP]
260
+ self.reset_channels[fn] = {
261
+ c for t in self.static[fn] if t[0] == catalog.ROLE_CHANNELS and t[2] == PIN_ROLE_RESET
262
+ for c in catalog.bitmap_to_channels(struct.unpack_from("<H", t, 3)[0], t[5:2 + t[1]])}
255
263
  self.max_connections[fn] = next((t[2] for t in self.static[fn] if t[0] == fake.MAX_CONNECTIONS), 1)
256
264
  self.targets: dict[tuple[int, tuple[int, int]], FakeTarget] = {
257
265
  (fn, p): FakeTarget() for fn in sorted(self.pairs) for p in self.pairs[fn]}
@@ -575,17 +583,18 @@ class Endpoint:
575
583
  got.append(struct.unpack_from("<HBH", value))
576
584
  elif tag & m.TAG_CRITICAL:
577
585
  return m.REJECTED, m.UNSUPPORTED, bytes([tag])
578
- self._check_plan(got)
579
586
  named = {fn for fn, _, _ in got}
587
+ if named & self.plan_from_config: # the settings' plan is the settings' (core §8)
588
+ raise Reject(m.UNAVAILABLE)
589
+ self._check_plan(got)
580
590
  self.plan = {a for a in self.plan if a[0] not in named} | set(got)
581
- self.plan_from_config -= named
582
591
  return m.COMPLETED, m.SUCCESS, b""
583
592
  if op == m.OP_PLAN_RELEASE: # n(u8) n x fn(u16); n = 0: every fn
584
593
  n = t.take("B")
585
594
  fns = {t.take("H") for _ in range(n)}
586
595
  t.tail()
587
- for fn in {a[0] for a in self.plan if not fns or a[0] in fns}:
588
- self._drop_plan(fn)
596
+ for fn in {a[0] for a in self.plan if not fns or a[0] in fns} - self.plan_from_config:
597
+ self._drop_plan(fn) # the settings' plans stay, n = 0 too (core §8)
589
598
  return m.COMPLETED, m.SUCCESS, b""
590
599
  return m.REJECTED, m.UNKNOWN_OPERATION, b""
591
600
 
@@ -657,10 +666,14 @@ class Endpoint:
657
666
  if method > 1:
658
667
  raise Reject(m.UNSUPPORTED)
659
668
  else:
660
- channel, _hold = t.take("HH")
661
- if channel not in (0xFFFF, self._channel_named("NRST")):
669
+ channel, _hold = t.take("HH") # no default reset line (oep-if-debug §3)
670
+ if channel not in self.reset_channels.get(fn, set()) or any(a[2] == channel for a in self.plan):
662
671
  raise Reject(m.UNAVAILABLE)
663
- got, ignored = t.tail({0x01, 0x03})
672
+ rvswd = self.names[fn] == "oep.wire.rvswd"
673
+ got, ignored = t.tail({0x01, 0x03, 0x04} if rvswd else {0x01, 0x03})
674
+ if 0x04 in got and (len(got[0x04]) != 1 or got[0x04][0] > 1):
675
+ raise Reject(m.MALFORMED)
676
+ idle_clock = got[0x04][0] if 0x04 in got else 0
664
677
  pair = self._pick_pair(fn, got)
665
678
  tg = self.targets[(fn, pair)]
666
679
  speed = min(4_000_000, struct.unpack("<I", got[0x01])[0]) if 0x01 in got else 4_000_000
@@ -676,6 +689,7 @@ class Endpoint:
676
689
  flags |= 2
677
690
  self.conns[cid].speed = min(self.conns[cid].speed, speed)
678
691
  c = self.conns[cid]
692
+ c.idle_clock = idle_clock # an existing connection takes the new rest level
679
693
  c.users.add("host")
680
694
  for n, s in self.slots.items(): # a new connection for an evicted slot: a new cue
681
695
  if s.wire_fn == fn and s.pair == pair:
@@ -1218,7 +1232,7 @@ class Endpoint:
1218
1232
 
1219
1233
  def _parse_slot(self, v: bytes) -> Slot:
1220
1234
  t = Take(v)
1221
- n, wire_fn, swdio, swclk, attach, retry_s, mech, name_len = t.take("BHHHBHBB")
1235
+ n, wire_fn, swdio, swclk, attach, retry_s, max_speed, idle_clock, mech, name_len = t.take("BHHHBHIBBB")
1222
1236
  name = t.bytes(name_len)
1223
1237
  scheme = t.take("B")
1224
1238
  rest = v[t.at:]
@@ -1226,6 +1240,8 @@ class Endpoint:
1226
1240
  raise Reject(m.MALFORMED)
1227
1241
  if retry_s and attach != SLOT_ATTACH["at_boot"]:
1228
1242
  raise Reject(m.MALFORMED)
1243
+ if idle_clock > 1 or (idle_clock and self.names.get(wire_fn) != "oep.wire.rvswd"):
1244
+ raise Reject(m.MALFORMED)
1229
1245
  if not SLOT_NAME.fullmatch(name.decode("ascii", "replace")):
1230
1246
  raise Reject(m.MALFORMED)
1231
1247
  if self.names.get(wire_fn) not in WIRES or (swdio, swclk) not in self.pairs.get(wire_fn, []):
@@ -1239,7 +1255,7 @@ class Endpoint:
1239
1255
  lock = (scheme, rest[:len(rest) // 2], rest[len(rest) // 2:])
1240
1256
  elif rest:
1241
1257
  raise Reject(m.MALFORMED)
1242
- return Slot(n, wire_fn, (swdio, swclk), attach, retry_s, mech, name.decode(), lock)
1258
+ return Slot(n, wire_fn, (swdio, swclk), attach, retry_s, max_speed, idle_clock, mech, name.decode(), lock)
1243
1259
 
1244
1260
  def _parse_bind(self, v: bytes, slots: dict[int, Slot]) -> Bind:
1245
1261
  t = Take(v)
@@ -1291,7 +1307,9 @@ class Endpoint:
1291
1307
  if not tg.present:
1292
1308
  return
1293
1309
  try:
1294
- cid = self._seat(s.wire_fn, s.pair, tg, 4_000_000, evict=False) # automatic: never evicts
1310
+ speed = min(4_000_000, s.max_speed) if s.max_speed else 4_000_000 # the slot's line settings
1311
+ cid = self._seat(s.wire_fn, s.pair, tg, speed, evict=False) # automatic: never evicts
1312
+ self.conns[cid].idle_clock = s.idle_clock
1295
1313
  except Reject:
1296
1314
  return
1297
1315
  tg.havereset = False
oep_client/fake.py CHANGED
@@ -185,8 +185,8 @@ def esp32_v003() -> FakeProbe:
185
185
  _core("3.0.0", "esp32-d0wd", bytes.fromhex("0070070d9394"), 40, reserved, f"{NS}.esp32-v003",
186
186
  {16: "SWIO", 23: "NRST", 22: "DUT TX", 21: "DUT RX"},
187
187
  _transports([(TRANSPORT["uart_bridge"], 0xFF)])),
188
- Offered(1, 1, "oep.wire.swio", (catalog.channel_group(1, [(1, 16)]), catalog.u8(IMPLEMENTATION, 1),
189
- catalog.u8(MAX_CONNECTIONS, 1))),
188
+ Offered(1, 1, "oep.wire.swio", (catalog.channel_group(1, [(1, 16)]), catalog.role_channels(3, [23]),
189
+ catalog.u8(IMPLEMENTATION, 1), catalog.u8(MAX_CONNECTIONS, 1))),
190
190
  Offered(2, 1, "oep.target.riscv-dm", (catalog.u32(FEATURES, 0b0111), catalog.u8(IMPLEMENTATION, 1),
191
191
  catalog.u16(MAX_LENGTH, 40))),
192
192
  Offered(3, 1, "oep.target.console", (catalog.tlv(MECHANISMS, bytes([0, 1, 2])),)),
oep_client/fake_serve.py CHANGED
@@ -77,8 +77,8 @@ def build(a: argparse.Namespace) -> endpoint.Endpoint:
77
77
  swdio, swclk = ep.pairs[wire][n]
78
78
  mech = 2 if 2 in ep.mechanisms else min(ep.mechanisms)
79
79
  raw = name.encode()
80
- value = struct.pack("<BHHHBHBB", n, wire, swdio, swclk, reg.PROBE_CONFIG.enum["slot_attach"]["at_boot"], 1,
81
- mech, len(raw)) + raw + b"\x00" # no lock
80
+ value = struct.pack("<BHHHBHIBBB", n, wire, swdio, swclk, reg.PROBE_CONFIG.enum["slot_attach"]["at_boot"], 1,
81
+ 0, 0, mech, len(raw)) + raw + b"\x00" # no speed ceiling, rests high, no lock
82
82
  items.append(bytes([_ITEM["slot"], len(value)]) + value)
83
83
  if a.bind:
84
84
  streams = b"".join(struct.pack("<BH", reg.PROBE_CONFIG.enum["bind_stream"]["slot_console"], n)
oep_client/interfaces.py CHANGED
@@ -71,15 +71,14 @@ KNOWN: dict[str, Known] = {
71
71
  0x46: ("label", _label), 0x47: ("resets on open", lambda v: "yes"),
72
72
  0x49: ("transport", _transport), 0x4A: ("OEP VID:PID", lambda v: "yes" if v[:1] == b"\x01" else "no")}),
73
73
  "oep.wire.rvswd": Known("scan, attach, detach over RVSWD (attach returns a connection)",
74
- roles={1: "SWDIO", 2: "SWCLK"}, tags={0x40: ("max connections", lambda v: str(v[0]))}),
74
+ roles={1: "SWDIO", 2: "SWCLK", 3: "reset"}, tags={0x40: ("max connections", lambda v: str(v[0]))}),
75
75
  "oep.wire.swio": Known("scan, attach, detach over SWIO, one wire (attach returns a connection)",
76
- roles={1: "SWIO"}, tags={0x40: ("max connections", lambda v: str(v[0]))}),
76
+ roles={1: "SWIO", 3: "reset"}, tags={0x40: ("max connections", lambda v: str(v[0]))}),
77
77
  "oep.wire.swd": Known("scan, attach, detach over ARM SWD", roles={1: "SWDIO", 2: "SWCLK"},
78
78
  tags={0x40: ("max connections", lambda v: str(v[0]))}),
79
79
  "oep.target.riscv-dm": Known(
80
80
  "RISC-V Debug Module over DMI: step lists, block read/write, run until halt, halt/resume",
81
- features={0: "block read/write", 1: "run until halt", 2: "reset", 3: "step"},
82
- tags={0x40: ("clobbers", lambda v: ", ".join(f"0x{r:04x}" for r in struct.unpack(f"<{len(v) // 2}H", v)))}),
81
+ features={0: "block read/write", 1: "run until halt", 2: "reset", 3: "step"}),
83
82
  "oep.target.arm-adi": Known("ARM Debug Interface: DP/AP transfer lists, block transfers"),
84
83
  "oep.target.console": Known(
85
84
  "console streams on a debug connection (position-addressed, marks)",
oep_client/registry.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  from types import SimpleNamespace as _NS
4
4
 
5
- REGISTRY_HASH = "7ba704094c2898d2"
5
+ REGISTRY_HASH = "dd6d527421bb6206"
6
6
  SCHEMA = 1
7
7
  PROTOCOL_REVISION = 1
8
8
  CONFIRM_REQUEST_MAGIC = 'OEP?'
@@ -22,19 +22,19 @@ TIMING = {"resync_quiet_ms": 0x32, "probe_frame_gap_ms": 0xC8, "host_frame_pause
22
22
 
23
23
  INTERFACES = {}
24
24
  CORE = _NS(name="oep.core", revision=1, op={"confirm": 0x01, "list": 0x02, "describe": 0x03, "plan_apply": 0x04, "plan_release": 0x05, "open": 0x10, "end": 0x11, "keepalive": 0x12, "lock_state": 0x13, "subscribe": 0x30, "unsubscribe": 0x32, "link_source": 0x40, "link_sink": 0x41}, lock_free={0x01, 0x02, 0x03, 0x10, 0x13, 0x40, 0x41},
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}}, event={"heartbeat": 0x01}, enum={"transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}}, own={})
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}, "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, "swclk": 0x02}}, own={})
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={})
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}}, own={})
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={})
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
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={})
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
- closed_tail={}, tlv={"describe": {"clobbers": 0x40}, "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={})
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={})
38
38
  INTERFACES["oep.target.riscv-dm"] = TARGET_RISCV_DM
39
39
  TARGET_ARM_ADI = _NS(name="oep.target.arm-adi", revision=1, op={"transfer": 0x01, "read_block": 0x02, "write_block": 0x03}, lock_free={},
40
40
  closed_tail={}, tlv={}, event={}, enum={}, own={})
oep_client/riscv.py CHANGED
@@ -14,8 +14,8 @@ import struct
14
14
  import time
15
15
  from dataclasses import dataclass, field
16
16
 
17
- from . import host as h, message as m, registry as reg
18
- from .core import Interface
17
+ from . import catalog, host as h, message as m, registry as reg
18
+ from .core import Interface, describe
19
19
  from .fixture import Gpio
20
20
 
21
21
  STATUS = reg.STATUS
@@ -109,7 +109,9 @@ class WireBase(Interface):
109
109
  class Wire(WireBase):
110
110
  """oep.wire.rvswd / oep.wire.swio (CH32 debug links to a RISC-V debug module)."""
111
111
  NAME = "oep.wire.rvswd"
112
- DEFAULT_RESET = 0xFFFF
112
+ ROLE_RESET = reg.WIRE_RVSWD.enum["pin_role"]["reset"]
113
+ TAG_IDLE_CLOCK = reg.WIRE_RVSWD.tlv["attach"]["idle_clock"]
114
+ IDLE_CLOCK = reg.WIRE_RVSWD.enum["idle_clock"]
113
115
  RUN, HALT = reg.WIRE_RVSWD.enum["attach_method"]["run"], reg.WIRE_RVSWD.enum["attach_method"]["halt"]
114
116
  TAG_TARGET_ID = reg.WIRE_RVSWD.tlv["attach_answer"]["target_id"]
115
117
  SCHEME_WCH_DMI_7F = reg.WIRE_RVSWD.enum["target_id_scheme"]["wch_dmi_7f"]
@@ -125,12 +127,28 @@ class Wire(WireBase):
125
127
  v = tail.get(self.TAG_TARGET_ID)
126
128
  self.target_id = (v[0], bytes(v[1:])) if v else None
127
129
 
128
- def attach(self, halt: bool = True, max_speed: int | None = None, pins: tuple[int, int] | None = None) -> tuple[int, int]:
130
+ def _idle_tlv(self, idle_clock: str | None) -> bytes:
131
+ # rvswd only, critical: a probe that cannot rest the line that way must refuse (oep-if-debug §3)
132
+ return b"" if idle_clock is None else m.tlv(self.TAG_IDLE_CLOCK, bytes([self.IDLE_CLOCK[idle_clock]]), critical=True)
133
+
134
+ def reset_channels(self) -> list[int]:
135
+ """The channels attach_under_reset may take (describe role_channels, role reset). There is no default reset
136
+ line: the host names one every time (oep-if-debug §3)."""
137
+ out = set()
138
+ for tag, v in describe(self.host, self.fn):
139
+ if tag & ~m.TAG_CRITICAL == catalog.ROLE_CHANNELS and v[0] == self.ROLE_RESET:
140
+ out.update(catalog.bitmap_to_channels(struct.unpack_from("<H", v, 1)[0], v[3:]))
141
+ return sorted(out)
142
+
143
+ def attach(self, halt: bool = True, max_speed: int | None = None, pins: tuple[int, int] | None = None,
144
+ idle_clock: str | None = None) -> tuple[int, int]:
129
145
  """-> (connection, DMSTATUS). Attaching an attached wire returns its connection as it is (self.existing).
130
146
  self.had_reset: a pending havereset was acknowledged first (a V00x's DMSTATUS halt / run bits stay frozen
131
147
  until then); self.speed_hz: the speed the probe chose; max_speed: a ceiling the probe must keep (critical);
132
- self.target_id: (scheme, value) of the target's identity when the probe could read one (oep-if-debug §1)."""
133
- body = bytes([self.HALT if halt else self.RUN]) + self._speed_tlv(max_speed) + self._pins_tlv(pins)
148
+ idle_clock: "high" / "low", how rvswd rests SWCLK (critical). Both are the target's, known by the host
149
+ (oep-if-debug §3). self.target_id: (scheme, value) of the target's identity when the probe could read one."""
150
+ body = (bytes([self.HALT if halt else self.RUN]) + self._speed_tlv(max_speed) + self._pins_tlv(pins)
151
+ + self._idle_tlv(idle_clock))
134
152
  rd = m.Reader(self._call(self.ATTACH, body).payload)
135
153
  conn, status, flags, self.speed_hz = rd.take("HIBI")
136
154
  self.had_reset, self.existing = bool(flags & 1), bool(flags & 2)
@@ -139,12 +157,13 @@ class Wire(WireBase):
139
157
  self._take_target_id(tail)
140
158
  return conn, status
141
159
 
142
- def attach_under_reset(self, channel: int | None = None, hold_ms: int = 20,
143
- max_speed: int | None = None, pins: tuple[int, int] | None = None) -> tuple[int, int]:
144
- """Hold the target in reset through `channel` (None: the probe's default reset line), attach, release and
145
- halt it at once - the way back from firmware that turns the debug pins into GPIOs. -> (connection, dpc)"""
146
- body = (struct.pack("<HH", self.DEFAULT_RESET if channel is None else channel, hold_ms) + self._speed_tlv(max_speed)
147
- + self._pins_tlv(pins))
160
+ def attach_under_reset(self, channel: int, hold_ms: int = 20, max_speed: int | None = None,
161
+ pins: tuple[int, int] | None = None, idle_clock: str | None = None) -> tuple[int, int]:
162
+ """Hold the target in reset through `channel` (always named: there is no default reset line; the probe
163
+ allows reset_channels()), attach, release and halt it at once - the way back from firmware that turns the
164
+ debug pins into GPIOs. -> (connection, dpc)"""
165
+ body = (struct.pack("<HH", channel, hold_ms) + self._speed_tlv(max_speed) + self._pins_tlv(pins)
166
+ + self._idle_tlv(idle_clock))
148
167
  rd = m.Reader(self._call(self.ATTACH_UNDER_RESET, body).payload)
149
168
  conn, dpc, self.speed_hz = rd.take("HII")
150
169
  self._take_target_id(rd.tail())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.5
3
+ Version: 0.0.7
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
@@ -1,36 +1,36 @@
1
- oep_client/__init__.py,sha256=arvOlIBZyy_QgLJjeCKna1018c1f7GeNCDiwrNIscWc,163
2
- oep_client/__main__.py,sha256=Ta7D7jhtg5IhFWuI3KTikbDNYazh9QKCH3FtMMnDMjg,14143
1
+ oep_client/__init__.py,sha256=Eo4zAyMLP7Kn4ZVfKG_wGeLWVvEnMO3lQdewP4ORx6k,163
2
+ oep_client/__main__.py,sha256=qtVGdpAHULBtLY2SedabJf-uwDZZhMf_YmrbqRzM57U,14669
3
3
  oep_client/arm.py,sha256=JLyUhLSPcmsk73schZcaJYoOKuHzrvnU8kPmfrEvVuo,12331
4
- oep_client/capture.py,sha256=RZgDnWqr8Aqy49U7PqQpnKIQEL18djVuDJFydxrmv-c,17996
4
+ oep_client/capture.py,sha256=hs8mQU1gE1P67GOivxw2C2Ur8wimWqCdhtk1nqGN71Q,18092
5
5
  oep_client/catalog.py,sha256=EqAQnSgHFxGbCO2GQ_Ee-uheJKbBBUUpL1-0qHfNMXU,6574
6
6
  oep_client/ch32_flash.py,sha256=Fc-6jEj15cQtMSKmfC5dmDetMXJ8hAdAuJ9d9odwrsA,12651
7
7
  oep_client/cobs.py,sha256=hqddQI_X7rsBvBvcP_MBE3bXPyB13-Azjqp_9JfdQ1g,2288
8
- oep_client/config.py,sha256=JhEmQKU-4KjSAoIovgeEwueKEiMLPUIiGVhZcR5ZlVc,9271
8
+ oep_client/config.py,sha256=a8zlxPI0sG0Z8gwUyg9OuKbXHoB5FctCrRDHULHhxCo,9753
9
9
  oep_client/console.py,sha256=YYcuc9Qxe3kihla8wynYbDaLVr7wBLEvswrM-cMK3Tk,6859
10
10
  oep_client/core.py,sha256=Cyyaknjipkg6tS76dZe5GMqjmOoQjlfeP0WFtMHkJNs,10403
11
11
  oep_client/decode.py,sha256=M-bRARrA2qGZLoJn0BdXeTb4WjL_2Hb2nk_g1IGXIw4,2382
12
12
  oep_client/dump.py,sha256=GAnmu2abuDmGmbWcbbEfC3EUuPp5Hady9vQVNcQ-724,6743
13
- oep_client/endpoint.py,sha256=K6FWTojqKqKIQEPpuOQLmfbaDWLvspXed-ipzFNuKHY,72303
13
+ oep_client/endpoint.py,sha256=tRx9ZUIiFN0J9NS3OwZ89NorIV7XqmZbYeUMnvJb6bE,74082
14
14
  oep_client/esp32_targets.py,sha256=b8tNH-nb2KC-XgNfWg5l3TO0cyhZOF-gJajaVxNVSGQ,4988
15
- oep_client/fake.py,sha256=UzpB-A42Ma2QqJj0l-JmwCJkMn6DPVHHN77fcemT59U,13104
15
+ oep_client/fake.py,sha256=TJpzwSCFDSco-3HCE2ZHzusMx0r0dtrD1bkGn6aos90,13136
16
16
  oep_client/fake_serial.py,sha256=uKl0UNcifhdeykQtm8gnRRhqV7TT4vPwBFuBhbf-9BI,4504
17
- oep_client/fake_serve.py,sha256=dIuYxhlAixSb8_OQ4b4UXTkWqW5Tpf_F9uuqMNWn0F4,14612
17
+ oep_client/fake_serve.py,sha256=Ke_hGRiOayCyut-uYOa0PaOFzy-zlvPHGwjs4HEZkAQ,14644
18
18
  oep_client/fixture.py,sha256=653hCD-Iz0Lfs8dT4Hdg3QyubbbOGs1_xjq_3nb8foU,5564
19
19
  oep_client/frames.py,sha256=xjgjnXmXUqNJZ5m0h91r4Cy48WoVK1mEMW4gRg-mlMI,3991
20
20
  oep_client/hid_stream.py,sha256=99Dsm4Io1keukuETG_UNqjBGiVYZZeb__Pv4ytaKg9k,10695
21
21
  oep_client/host.py,sha256=Y5oqE-pw5Ja9cG1dzdy8svIEgR4Kqhki0wuyrpP_xH8,15563
22
- oep_client/interfaces.py,sha256=P4ehrIj4j-9eg4NjaVdV15pawI2vOqucJwVklSdR-As,5472
22
+ oep_client/interfaces.py,sha256=TNvof5blyLjO1Mc0OentKcMVBD_5e4riRG18UgEh2ZE,5380
23
23
  oep_client/link.py,sha256=J9VtBWfTgNoIgUQuj8SsH9hHesQM2uEN545IoxKVS8E,19406
24
24
  oep_client/message.py,sha256=MEM1UT6KFM7AsT56jvBLt_FJK_6SJ7O_nGAiipW0_zo,9912
25
25
  oep_client/names.py,sha256=ztCZFWs__aR-a5G14TnJP-Q4YfLhbyrzvOVOMJuG93k,3653
26
- oep_client/registry.py,sha256=nRM-sQiAMs1OzuSZIajdsBfdFckYo3OfwgmcLkHzxqw,9867
27
- oep_client/riscv.py,sha256=E0_C-4mBsrp9FUoZpMiqSH9e8sC8Q4EO5HRjFKE6_pc,21392
26
+ oep_client/registry.py,sha256=uO9Ilx_pPPhLD-17TosgXH9QfI11DymYNIR-EacaZOw,9984
27
+ oep_client/riscv.py,sha256=PNeo_HXlq08b4h083olOOrGxqejD6ggQ7QzVqkIHEMA,22621
28
28
  oep_client/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
29
29
  oep_client/target.py,sha256=R8BvYfFfYNhZF0MysJkNb8b69YgBlmeGXNX8ReZIe5k,1068
30
30
  oep_client/uiapduino.py,sha256=IEPG3LbZPksi8g3wmAf6T0CRWSfYKW1_qrH_GBVQdxY,6090
31
31
  oep_client/usb_stream.py,sha256=VJMCxsOlQCk005FcS92pqTh33fRSFBizoL__L0VbUMA,10242
32
- oep_client_python-0.0.5.dist-info/METADATA,sha256=42SpXvaLTywLE9L3yPq1mRj4UNTamkB7Vb0dbZegO24,7368
33
- oep_client_python-0.0.5.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
34
- oep_client_python-0.0.5.dist-info/entry_points.txt,sha256=h6_8QX89Ww1ogALEGG5g4kspT3IOV6O51lwKqf5BWco,49
35
- oep_client_python-0.0.5.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
36
- oep_client_python-0.0.5.dist-info/RECORD,,
32
+ oep_client_python-0.0.7.dist-info/METADATA,sha256=Shg-dYEbV4747Sl1HZlRz-AxoIdVQ2Z9LzszmjCTdnY,7368
33
+ oep_client_python-0.0.7.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
34
+ oep_client_python-0.0.7.dist-info/entry_points.txt,sha256=h6_8QX89Ww1ogALEGG5g4kspT3IOV6O51lwKqf5BWco,49
35
+ oep_client_python-0.0.7.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
36
+ oep_client_python-0.0.7.dist-info/RECORD,,