oep-client-python 0.0.25__tar.gz → 0.0.26__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.25 → oep_client_python-0.0.26}/CHANGELOG.md +6 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/PKG-INFO +8 -3
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/README.ja.md +5 -2
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/README.md +7 -2
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/pyproject.toml +1 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/__init__.py +1 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/__main__.py +3 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/arm.py +1 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/capture.py +3 -2
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/config.py +3 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/core.py +15 -5
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/endpoint.py +24 -4
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/fake_serial.py +34 -5
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/fake_serve.py +7 -4
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/host.py +30 -5
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/link.py +220 -65
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/riscv.py +20 -3
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_port_speed.py +126 -1
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/.gitignore +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/LICENSE +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/catalog.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/ch32_flash.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/cobs.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/console.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/decode.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/dump.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/fake.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/fake_capture.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/fixture.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/frames.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/hid_stream.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/interfaces.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/message.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/names.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/registry.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/rp2350.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/uiapduino.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/src/oep_client/usb_stream.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_capabilities.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_capture.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_cobs.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_config.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_config_disable.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_fake_capture.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_fake_spec.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_fixture_targets.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_flash_console.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_hid_stream.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_interfaces.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_link_host.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_link_serial.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_session.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_target_parts.py +0 -0
- {oep_client_python-0.0.25 → oep_client_python-0.0.26}/tests/test_usb_stream.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.26
|
|
6
|
+
- (EN) A request that may take long on the probe is waited for that long (core §6.1: the probe does not count the lease while it runs one): `Host.expecting(ms)` (and `request(..., expect_ms=)` / `call(..., expect_ms=)`) makes the link wait at least that and 0.5 s (`link.EXPECT_MARGIN_S`) for each answer, at any rate - so the raised-rate wait of a quarter of the lease applies to ordinary requests only. Passed down by riscv-dm run (`timeout_ms`), dmi (its wait_us and poll_us steps, `riscv.dmi_wait_ms`), attach with a reset (`hold_ms`, riscv and arm), capture start (the configured `blocking_ms`; a group's the longest of its tracks) and probe.config save (`config.SAVE_EXPECT_MS`, 2 s). Such a request that times out at a raised rate still falls back. The link's own confirms, keepalives, the step down's revert and `pump` never wait longer. Fix: the keepalive a raised link sends after 1 s of quiet now goes out before the request takes its corr (`Host.before_request`); it used to go out with a higher corr first, so the probe took the request for an old one (rejected result_lost).
|
|
7
|
+
- (JA) probe で長くかかりうる要求はその間待つ(core §6.1: probe は要求の実行中 lease を数えない): `Host.expecting(ms)`(と `request(..., expect_ms=)` / `call(..., expect_ms=)`)で、link はどの速さでも応答ごとに少なくともその時間と 0.5 秒(`link.EXPECT_MARGIN_S`)待つ。上げた速さでの lease の 4 分の 1 の待ちは普通の要求だけに掛かる。riscv-dm の run(`timeout_ms`)、dmi(wait_us と poll_us の step の和、`riscv.dmi_wait_ms`)、reset 付きの attach(`hold_ms`、riscv と arm)、capture の start(configure の `blocking_ms`。組はトラックの最長)、probe.config の save(`config.SAVE_EXPECT_MS`、2 秒)が渡す。そうした要求も上げた速さで時間切れなら戻る。link 自身の confirm・keepalive・降りるときの戻す・`pump` は長く待たない。修正: 上げた link が 1 秒黙った後に送る keepalive を、要求が corr を取る前に送るようにした(`Host.before_request`)。これまでは大きい corr で先に出ていたため、probe が要求を古いものとみなした(rejected result_lost)。
|
|
8
|
+
- (EN) port_speed (core §3.5 items 3 and 7), after a V003 jig (classic ESP32 + CH340) passed 921600 each way alone but broke a frame every ~0.5 s under sustained duplex use, the probe reverted by itself and the session's lease lapsed: `raise_speed` adds a duplex phase after the in / out phases - link_source and link_sink (max_frame) interleaved, pipelined at the in-flight that passed, for `duplex_s` (1.0) and at least `duplex_frames` (64); a broken frame fails the rate ("broke both ways at once"). `SpeedTrial.duplex_bytes` / `duplex_kb_s` / `broken_duplex`, `SpeedReport.duplex_kb_s`, a duplex column in `to_text()`, `oep speed --duplex-s`. In use: while a raised rate is in force, the link counts broken frames and resends; 3 within 5 s (`STRIKE_MAX`, `STRIKE_WINDOW_S`) step down - port_speed step 2 at the raised rate (0.2 s, never sent again), the boot speed, a confirm, the request again there. A request at a raised rate that gets no answer at all keeps the old fallback (back, confirm, once more there), but each wait is now at most a quarter of the lease (at least 0.3 s), so it ends well inside the lease. Either way the rate is not used again in that session (`SpeedReport.stepped_down`, `.down_why`, a line in `to_text()`; `raise_speed` skips it, "stepped down from earlier in this session"). Fake: `BrokenRate(duplex=True)` breaks only while a request of `min_size` comes in with such an answer still unread (the request towards the probe, that answer towards the host), `BrokenRate(every=N)` only every Nth frame; `fake_serve --broken-rate RATE[:MIN][:in|out][:duplex][:everyN]`.
|
|
9
|
+
- (JA) port_speed(core §3.5 の 3 と 7)。V003 のジグ(classic ESP32 + CH340)で 921600 が向きごとには通ったのに、両方向を続けて使うと約 0.5 秒ごとにフレームが壊れ、probe が自分で戻り、セッションの lease が切れたため: `raise_speed` は in / out の後に両方向同時の確かめを足した - link_source と link_sink(max_frame)を交互に、通った同時の数で並べ、`duplex_s`(1.0)かつ `duplex_frames`(64)以上。1 つでも壊れればその速さは使わない("broke both ways at once")。`SpeedTrial.duplex_bytes` / `duplex_kb_s` / `broken_duplex`、`SpeedReport.duplex_kb_s`、`to_text()` に duplex の列、`oep speed --duplex-s`。使っている間: 上げた速さの間、link は壊れたフレームと送り直しを数え、5 秒の内に 3 つ(`STRIKE_MAX`、`STRIKE_WINDOW_S`)で降りる - 上げた速さで port_speed の step 2(0.2 秒、送り直さない)、起動時の速さ、confirm、要求をそこでもう一度。上げた速さで応答がまったく来ない要求はこれまでの戻り方(戻る、confirm、そこでもう一度)のままだが、待つ 1 回を lease の 4 分の 1(最短 0.3 秒)までにしたので lease の内に十分収まる。どちらでもその速さはそのセッションの間使わない(`SpeedReport.stepped_down`、`.down_why`、`to_text()` に 1 行。`raise_speed` は飛ばす: "stepped down from earlier in this session")。偽の probe: `BrokenRate(duplex=True)` は `min_size` 以上の要求が、同じ大きさの応答が読まれないうちに来たときだけ壊す(probe へはその要求、host へはその応答)、`BrokenRate(every=N)` は N 個に 1 つだけ壊す。`fake_serve --broken-rate RATE[:MIN][:in|out][:duplex][:everyN]`。
|
|
10
|
+
|
|
5
11
|
## 0.0.25
|
|
6
12
|
- (EN) port_speed's idle limit (oep-spec 877cf01, core §3.5 items 5 and 6; registry synced, `port_speed_idle_max_ms` = 3000): a committed rate goes back after at most 3 s with no good frame, so a host that raised the speed and died leaves it no longer than that. The fake clamps idle_ms the same way (0 and anything longer: the maximum; 0 used to mean never). `raise_speed`'s `idle_ms` defaults to the maximum (0 or more than it: the maximum). While raised, the link sends a keepalive before a request (or a pipelined exchange, or inside a long `pump`) when it has been quiet for 1 s; `SerialLink.keep_alive()` does the same for a caller that sits idle for long (no background thread). `open_host` on a serial port (COBS) retries its first confirm for about 4 s (`OPEN_RETRY_S`, the idle maximum and a second; 0.5 s each) to wait out a rate left over, then raises TimeoutError; other transports keep their timeout.
|
|
7
13
|
- (JA) port_speed の黙りの上限(oep-spec 877cf01、core §3.5 の 5 と 6。registry を写し直した。`port_speed_idle_max_ms` = 3000): 決めた速さは正しいフレームが来ないまま最長 3 秒で戻るので、速さを上げた host が落ちてもそれより長くは残らない。fake も idle_ms を同じに切り詰める(0 とそれより長い値は最長。0 はこれまで戻らないだった)。`raise_speed` の `idle_ms` の既定は最長(0 と最長より長い値も最長)。上げている間、link は 1 秒黙っていれば要求の前(並べた送り、長い `pump` の中も)に keepalive を送る。長く黙る呼び出し側は `SerialLink.keep_alive()` で同じことをする(裏のスレッドは無い)。シリアルの口(COBS)の `open_host` は最初の confirm を約 4 秒(`OPEN_RETRY_S`、黙りの最長と 1 秒。1 回 0.5 秒)繰り返して残った速さが戻るのを待ち、それでも答えが無ければ TimeoutError。ほかの経路の時間切れは変わらない。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.26
|
|
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
|
|
@@ -122,7 +122,9 @@ print(hst.link.speed.to_text()) # every rate tried: actual, in / out
|
|
|
122
122
|
|
|
123
123
|
Each rate in order: `try` (answered at the speed now, then the probe switches) -> the host switches -> a verify both ways
|
|
124
124
|
with max_frame-sized frames (link_source / link_sink, pipelined) for `verify_bytes` or `verify_s`, counting broken frames
|
|
125
|
-
and measuring KB/s each way ->
|
|
125
|
+
and measuring KB/s each way -> both ways at once at the in-flight that passed (link_source and link_sink interleaved,
|
|
126
|
+
about 1 s and at least 64 frames, `duplex_kb_s`; a line that carries each way alone can still break under duplex use - a
|
|
127
|
+
CH340 at 921600 did) -> `commit` when nothing broke, else revert and back to the boot speed, confirmed there (a
|
|
126
128
|
probe whose revert was lost waits out its `verify_ms`). The first rate that passes is kept; a rate the probe's UART cannot
|
|
127
129
|
make is skipped. Which rates pass depends on the bridge chip and its driver (an FTDI took only 3 MHz / n, a CH340 921600
|
|
128
130
|
but not 1500000 towards the host: oep-spec docs/uart-speed-negotiation.ja.md), so the host chooses them. The report
|
|
@@ -133,7 +135,10 @@ line goes quiet (`idle_ms`, at most `port_speed_idle_max_ms` = 3 s; a host that
|
|
|
133
135
|
that): while raised the link sends a keepalive before a request when it has been quiet for 1 s, and `hst.link.keep_alive()`
|
|
134
136
|
does the same for a caller that sits idle for long; opening a serial port retries its first confirm for about 4 s to
|
|
135
137
|
wait out a rate left over. The link follows an `end` at once, and a request that goes unanswered at a raised rate (its
|
|
136
|
-
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges
|
|
138
|
+
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges; while raised
|
|
139
|
+
each wait for an answer is at most a quarter of the lease, so this ends well inside it. In use, 3 broken frames or
|
|
140
|
+
resends within 5 s step the link down: port_speed revert at the raised rate, the boot speed, a confirm, the request
|
|
141
|
+
again there. A rate left either way is not used again in that session (`speed.stepped_down`, `speed.down_why`). A probe
|
|
137
142
|
without the feature answers `not supported` and stays at its speed. Behind a broker (TCP) the broker does this, not the
|
|
138
143
|
client. Serial ports are also opened in the driver's low-latency mode where it has one (an FTDI's latency timer 16 -> 1 ms
|
|
139
144
|
tripled a UART bridge's throughput). `open_host(..., baud=)` names the boot speed when the board's profile is not 115200.
|
|
@@ -95,7 +95,8 @@ print(hst.link.speed.to_text()) # 試した速さごとに、実際
|
|
|
95
95
|
|
|
96
96
|
速さごとに順に: `試す`(今の速さで応答してから probe が切り替える)→ host も切り替える → 両方向に max_frame の大きさのフレームで
|
|
97
97
|
(link_source / link_sink、パイプライン)`verify_bytes` か `verify_s` の分だけ確かめ、壊れたフレームを数え、それぞれの向きの KB/s を
|
|
98
|
-
測る →
|
|
98
|
+
測る → 通った同時の数で両方向を同時に(link_source と link_sink を交互に、約 1 秒、64 フレーム以上。`duplex_kb_s`。向きごとには
|
|
99
|
+
通っても同時に流すと壊れる線がある。921600 の CH340 がそうだった)流す → 何も壊れなければ `決める`、壊れれば戻して起動時の速さに戻り、そこで confirm し直す(戻すが届かなかった probe は `verify_ms`
|
|
99
100
|
を待って戻る)。最初に通った速さを使う。probe の UART が作れない速さは飛ばす。通る速さは変換チップとドライバで決まる(FTDI は
|
|
100
101
|
3 MHz ÷ n だけ、CH340 は 921600 は通り 1500000 は probe → host が壊れた: oep-spec docs/uart-speed-negotiation.ja.md)ので、速さは
|
|
101
102
|
host が選ぶ。結果(`link.speed`: `rate`、`chosen`、`in_kb_s` / `out_kb_s`、`trials`)はキャプチャや書き込みの予算を立てるのに使う。
|
|
@@ -104,7 +105,9 @@ probe はセッションが終わったとき(`end`、lease の期限切れ、
|
|
|
104
105
|
`port_speed_idle_max_ms` = 3 秒。落ちた host の速さもそれより長くは残らない)に自分で起動時の速さに戻る: 上げている間、link は
|
|
105
106
|
1 秒黙っていれば要求の前に keepalive を送り、長く黙る呼び出し側は `hst.link.keep_alive()` で同じことをする。シリアルの口を開くと
|
|
106
107
|
最初の confirm を約 4 秒繰り返して、残った速さが戻るのを待つ。link は `end` にはすぐ合わせ、上げた速さで応答の来ない要求(送り直しも)があれば、起動時の速さに戻って
|
|
107
|
-
confirm
|
|
108
|
+
confirm し、そこでもう一度送る(固まらない)。上げている間は応答を待つ 1 回が lease の 4 分の 1 までなので、lease の内に十分収まる。
|
|
109
|
+
使っている間に 5 秒の内に 3 回フレームが壊れるか送り直すと、上げた速さで port_speed の戻すを送り、起動時の速さに戻って confirm し、
|
|
110
|
+
要求をそこで送る。どちらで離れた速さもそのセッションの間は使わない(`speed.stepped_down`、`speed.down_why`)。機能の無い probe は `not supported` で、速さは変わらない。ブローカー(TCP)の後ろでは
|
|
108
111
|
client ではなくブローカーが行う。シリアルの口は、ドライバにあれば low-latency のモードで開く(FTDI の latency timer 16 → 1 ms で
|
|
109
112
|
UART bridge の速度が 3 倍になった)。ボードの起動時の速さが 115200 でなければ `open_host(..., baud=)` で渡す。
|
|
110
113
|
|
|
@@ -100,7 +100,9 @@ print(hst.link.speed.to_text()) # every rate tried: actual, in / out
|
|
|
100
100
|
|
|
101
101
|
Each rate in order: `try` (answered at the speed now, then the probe switches) -> the host switches -> a verify both ways
|
|
102
102
|
with max_frame-sized frames (link_source / link_sink, pipelined) for `verify_bytes` or `verify_s`, counting broken frames
|
|
103
|
-
and measuring KB/s each way ->
|
|
103
|
+
and measuring KB/s each way -> both ways at once at the in-flight that passed (link_source and link_sink interleaved,
|
|
104
|
+
about 1 s and at least 64 frames, `duplex_kb_s`; a line that carries each way alone can still break under duplex use - a
|
|
105
|
+
CH340 at 921600 did) -> `commit` when nothing broke, else revert and back to the boot speed, confirmed there (a
|
|
104
106
|
probe whose revert was lost waits out its `verify_ms`). The first rate that passes is kept; a rate the probe's UART cannot
|
|
105
107
|
make is skipped. Which rates pass depends on the bridge chip and its driver (an FTDI took only 3 MHz / n, a CH340 921600
|
|
106
108
|
but not 1500000 towards the host: oep-spec docs/uart-speed-negotiation.ja.md), so the host chooses them. The report
|
|
@@ -111,7 +113,10 @@ line goes quiet (`idle_ms`, at most `port_speed_idle_max_ms` = 3 s; a host that
|
|
|
111
113
|
that): while raised the link sends a keepalive before a request when it has been quiet for 1 s, and `hst.link.keep_alive()`
|
|
112
114
|
does the same for a caller that sits idle for long; opening a serial port retries its first confirm for about 4 s to
|
|
113
115
|
wait out a rate left over. The link follows an `end` at once, and a request that goes unanswered at a raised rate (its
|
|
114
|
-
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges
|
|
116
|
+
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges; while raised
|
|
117
|
+
each wait for an answer is at most a quarter of the lease, so this ends well inside it. In use, 3 broken frames or
|
|
118
|
+
resends within 5 s step the link down: port_speed revert at the raised rate, the boot speed, a confirm, the request
|
|
119
|
+
again there. A rate left either way is not used again in that session (`speed.stepped_down`, `speed.down_why`). A probe
|
|
115
120
|
without the feature answers `not supported` and stays at its speed. Behind a broker (TCP) the broker does this, not the
|
|
116
121
|
client. Serial ports are also opened in the driver's low-latency mode where it has one (an FTDI's latency timer 16 -> 1 ms
|
|
117
122
|
tripled a UART bridge's throughput). `open_host(..., baud=)` names the boot speed when the board's profile is not 115200.
|
|
@@ -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.26"
|
|
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"
|
|
@@ -36,6 +36,7 @@ def main(argv=None) -> int:
|
|
|
36
36
|
sp.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed (default 115200)")
|
|
37
37
|
sp.add_argument("--verify-bytes", type=int, default=32768)
|
|
38
38
|
sp.add_argument("--verify-s", type=float, default=1.0)
|
|
39
|
+
sp.add_argument("--duplex-s", type=float, default=1.0, help="both ways at once for this long (default 1)")
|
|
39
40
|
sp.add_argument("--json", action="store_true")
|
|
40
41
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
41
42
|
src = d.add_mutually_exclusive_group(required=True)
|
|
@@ -69,7 +70,8 @@ def _speed(args) -> int:
|
|
|
69
70
|
hst = link.open_host(args.probe, baud=args.baud)
|
|
70
71
|
try:
|
|
71
72
|
core.take(hst, 5000, owner="oep speed")
|
|
72
|
-
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s
|
|
73
|
+
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s,
|
|
74
|
+
duplex_s=args.duplex_s)
|
|
73
75
|
if args.json:
|
|
74
76
|
sys.stdout.write(json.dumps(dataclasses.asdict(report), indent=2) + "\n")
|
|
75
77
|
else:
|
|
@@ -37,7 +37,7 @@ class SwdWire(WireBase):
|
|
|
37
37
|
body = bytes([0]) + self._speed_tlv(self._speed_or_default(max_speed)) + self._pins_tlv(pins) + self._reset_tlv(reset)
|
|
38
38
|
if targetsel is not None:
|
|
39
39
|
body += m.tlv(self.TAG_TARGETSEL, struct.pack("<I", targetsel), critical=True)
|
|
40
|
-
rd = m.Reader(self._call(self.ATTACH, body).payload)
|
|
40
|
+
rd = m.Reader(self._call(self.ATTACH, body, expect_ms=reset[1] if reset else 0).payload)
|
|
41
41
|
conn, dpidr, self.flags, self.speed_hz = rd.take("HIBI")
|
|
42
42
|
self.existing = bool(self.flags & self.FLAGS["existing"])
|
|
43
43
|
rd.tail()
|
|
@@ -335,7 +335,7 @@ class LogicCapture(Interface):
|
|
|
335
335
|
|
|
336
336
|
def start(self) -> int:
|
|
337
337
|
"""-> blocking_ms (0: the probe keeps answering while it captures). self.generation: the new capture's."""
|
|
338
|
-
rd = m.Reader(self._call(self.START).payload)
|
|
338
|
+
rd = m.Reader(self._call(self.START, expect_ms=self.config.blocking_ms if self.config else 0).payload)
|
|
339
339
|
blocking, self.generation = rd.take("II")
|
|
340
340
|
rd.tail()
|
|
341
341
|
self.armed_s = time.monotonic()
|
|
@@ -576,7 +576,8 @@ class CaptureGroup(Interface):
|
|
|
576
576
|
def start(self, tracks: list[LogicCapture] = ()) -> tuple[int, int]:
|
|
577
577
|
"""-> (blocking_ms, the group's start_ns). `tracks`: whose armed_s and generation to set (the answer's TLV
|
|
578
578
|
generations names each track's new generation; self.generations keeps them by fn)."""
|
|
579
|
-
rd = m.Reader(self._call(self.START
|
|
579
|
+
rd = m.Reader(self._call(self.START, expect_ms=max([t.config.blocking_ms for t in tracks if t.config] or [0]))
|
|
580
|
+
.payload)
|
|
580
581
|
blocking, start_ns = rd.take("IQ")
|
|
581
582
|
gens = rd.tail().get(GROUP_GENERATIONS) or b""
|
|
582
583
|
self.generations = {fn: g for fn, g in struct.iter_unpack("<HI", gens[:len(gens) // 6 * 6])}
|
|
@@ -292,6 +292,8 @@ class State:
|
|
|
292
292
|
binds: list[BindState] = field(default_factory=list)
|
|
293
293
|
|
|
294
294
|
|
|
295
|
+
SAVE_EXPECT_MS = 2000 # a save writes the probe's flash: the link waits at least this (Host.expecting)
|
|
296
|
+
|
|
295
297
|
class ProbeConfig(Interface):
|
|
296
298
|
NAME = "oep.probe.config"
|
|
297
299
|
REVISION = 1
|
|
@@ -346,7 +348,7 @@ class ProbeConfig(Interface):
|
|
|
346
348
|
|
|
347
349
|
def save(self) -> int:
|
|
348
350
|
"""Save the current settings (a probe with storage; the whole is replaced). -> the hash saved."""
|
|
349
|
-
return self._hash_answer(self._call(self.SAVE))
|
|
351
|
+
return self._hash_answer(self._call(self.SAVE, expect_ms=SAVE_EXPECT_MS))
|
|
350
352
|
|
|
351
353
|
def erase(self) -> None:
|
|
352
354
|
self._call(self.ERASE)
|
|
@@ -4,6 +4,8 @@ entry, and calls that raise unless they worked)."""
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import contextlib
|
|
8
|
+
|
|
7
9
|
import struct
|
|
8
10
|
|
|
9
11
|
from . import catalog, host as h, message as m, registry as reg
|
|
@@ -196,12 +198,20 @@ class Interface:
|
|
|
196
198
|
raise UnsupportedRevision(f"{self.name} (fn {self.fn}) is revision {rev} on this probe; this client "
|
|
197
199
|
f"speaks revision {self.REVISION} only")
|
|
198
200
|
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
+
def _expecting(self, ms: int):
|
|
202
|
+
"""Host.expecting(ms) when the host has it (a stand-in host may not)."""
|
|
203
|
+
expecting = getattr(self.host, "expecting", None) if ms else None
|
|
204
|
+
return expecting(ms) if expecting else contextlib.nullcontext()
|
|
205
|
+
|
|
206
|
+
def _call(self, op: int, body: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
207
|
+
with self._expecting(expect_ms):
|
|
208
|
+
return self.host.call(self.fn, op, self.prefix + body, locked=locked)
|
|
201
209
|
|
|
202
|
-
def _request(self, op: int, body: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
203
|
-
"""Rejections raise; completed results of any outcome come back for the caller to decode.
|
|
204
|
-
|
|
210
|
+
def _request(self, op: int, body: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
211
|
+
"""Rejections raise; completed results of any outcome come back for the caller to decode. expect_ms: how
|
|
212
|
+
long it may take on the probe (Host.expecting)."""
|
|
213
|
+
with self._expecting(expect_ms):
|
|
214
|
+
return self.host.request(self.fn, op, self.prefix + body, locked=locked)
|
|
205
215
|
|
|
206
216
|
def request(self, op: int, body: bytes = b"") -> tuple[int, int, bytes]:
|
|
207
217
|
"""The raw (fn, op, payload) of one operation, for Host.pipeline / pipeline_calls."""
|
|
@@ -39,7 +39,8 @@ define:
|
|
|
39
39
|
verify_ms without a commit or at a broken candidate, a commit after idle_ms (at most port_speed_idle_max_ms, 3000;
|
|
40
40
|
0 and anything longer count as that maximum) with no good frame or 3 broken
|
|
41
41
|
candidates within 1 s, the session's end reverts after its answer. The line itself is modelled by `broken_rates`
|
|
42
|
-
(rate -> BrokenRate): frames at such a rate break, from a size and in the directions given
|
|
42
|
+
(rate -> BrokenRate): frames at such a rate break, from a size and in the directions given, only both ways at once
|
|
43
|
+
(`duplex`), only every Nth (`every`) (`fake_serial` applies it).
|
|
43
44
|
|
|
44
45
|
Every other non-core fn gets two stand-in operations so the session rules can be exercised - FAKE ONLY, they mean
|
|
45
46
|
nothing on a real probe: 0x01 write(u32) changes state, 0x02 read -> u32 needs no lock.
|
|
@@ -317,6 +318,16 @@ class BrokenRate:
|
|
|
317
318
|
min_size: int = 0
|
|
318
319
|
to_host: bool = True
|
|
319
320
|
to_probe: bool = True
|
|
321
|
+
duplex: bool = False # only while both ways carry such frames at once (a request of min_size and more comes in
|
|
322
|
+
# while an answer of min_size and more is still unread): the request breaks (to_probe),
|
|
323
|
+
# the unread answer breaks (to_host)
|
|
324
|
+
every: int = 1 # of the frames that would break, only every Nth does (1: all)
|
|
325
|
+
seen: int = 0 # the frames that would have broken so far (`every` counts these)
|
|
326
|
+
|
|
327
|
+
def hit(self) -> bool:
|
|
328
|
+
"""One more frame that would break: True when this one does (`every`)."""
|
|
329
|
+
self.seen += 1
|
|
330
|
+
return self.seen % max(1, self.every) == 0
|
|
320
331
|
|
|
321
332
|
|
|
322
333
|
PORT_SPEED_TAG = reg.CORE.tlv["describe"]["port_speed"]
|
|
@@ -1218,10 +1229,19 @@ class Endpoint:
|
|
|
1218
1229
|
return self.speed_rate
|
|
1219
1230
|
return self.port_speed_base or 115200
|
|
1220
1231
|
|
|
1221
|
-
def breaks(self, port: int, size: int, to_host: bool) -> bool:
|
|
1222
|
-
"""The line model: a frame of `size` bytes on `port` at its rate now breaks (`broken_rates`).
|
|
1232
|
+
def breaks(self, port: int, size: int, to_host: bool, duplex: bool = False) -> bool:
|
|
1233
|
+
"""The line model: a frame of `size` bytes on `port` at its rate now breaks (`broken_rates`). duplex: the other
|
|
1234
|
+
way carries a frame of the rate's min_size or more at the same time (a BrokenRate with `duplex` breaks only
|
|
1235
|
+
then)."""
|
|
1236
|
+
b = self.broken_rates.get(self.port_baud(port))
|
|
1237
|
+
if b is None or size < b.min_size or not (b.to_host if to_host else b.to_probe) or (b.duplex and not duplex):
|
|
1238
|
+
return False
|
|
1239
|
+
return b.hit()
|
|
1240
|
+
|
|
1241
|
+
def duplex_rate(self, port: int) -> BrokenRate | None:
|
|
1242
|
+
"""The rate `port` runs at now when it breaks only both ways at once (fake_serial checks the unread answers)."""
|
|
1223
1243
|
b = self.broken_rates.get(self.port_baud(port))
|
|
1224
|
-
return b is not None and
|
|
1244
|
+
return b if b is not None and b.duplex else None
|
|
1225
1245
|
|
|
1226
1246
|
def speed_after_answer(self) -> None:
|
|
1227
1247
|
"""The answer that asked for a switch or a revert is out (at the old speed): now do it."""
|
|
@@ -17,7 +17,8 @@ bytes to send, or None to send nothing (fault injection for tests).
|
|
|
17
17
|
port_speed (core §3.5): every closed candidate tells the endpoint whether it was a frame (`Endpoint.speed_frame`), a
|
|
18
18
|
switch or revert a request asked for happens once its answer is queued (at the old speed), and the endpoint's
|
|
19
19
|
`broken_rates` break frames at the port's rate now: a candidate from the host is then not a frame, an answer or push
|
|
20
|
-
goes out with a spoiled CRC
|
|
20
|
+
goes out with a spoiled CRC (a `duplex` one only while a request of its size comes in with such an answer still
|
|
21
|
+
unread: the request, or that answer, breaks). `FakeSerialStream` is the port as a pyserial-shaped stream for an in-process host, with
|
|
21
22
|
the host's own `baudrate`: while it differs from the probe's, every byte either way arrives garbled.
|
|
22
23
|
"""
|
|
23
24
|
|
|
@@ -40,6 +41,7 @@ class FakeSerialPort:
|
|
|
40
41
|
self.frames: list[bytes] = [] # framed answers waiting to go out
|
|
41
42
|
self.answers = 0
|
|
42
43
|
self.garble: Callable[[bytes, int], bytes] | None = None # (frame, the rate it goes at) -> what arrives
|
|
44
|
+
self.spoiled: set[int] = set() # the waiting answers a duplex BrokenRate already broke
|
|
43
45
|
|
|
44
46
|
def feed(self, data: bytes) -> None:
|
|
45
47
|
now = self.ep.now()
|
|
@@ -65,8 +67,10 @@ class FakeSerialPort:
|
|
|
65
67
|
self.cand = None
|
|
66
68
|
if not body:
|
|
67
69
|
return # 0x00 0x00: an empty frame
|
|
70
|
+
size = len(body) + 2
|
|
71
|
+
duplex = self._both_ways(size)
|
|
68
72
|
try:
|
|
69
|
-
if self.ep.breaks(self.index,
|
|
73
|
+
if self.ep.breaks(self.index, size, to_host=False, duplex=duplex):
|
|
70
74
|
raise cobs.CorruptFrame("the line's rate breaks this frame")
|
|
71
75
|
msg = cobs.unframe(body)
|
|
72
76
|
except cobs.CorruptFrame:
|
|
@@ -90,6 +94,22 @@ class FakeSerialPort:
|
|
|
90
94
|
self.frames.append(self._line(wire))
|
|
91
95
|
self.ep.speed_after_answer() # port_speed: switch (or revert) now the answer is out
|
|
92
96
|
|
|
97
|
+
def _both_ways(self, size: int) -> bool:
|
|
98
|
+
"""A request of `size` bytes came in: with a BrokenRate that breaks only both ways at once (`duplex`), whether
|
|
99
|
+
an answer of its min_size or more is still unread (both ways busy) - and then that answer breaks if the rate
|
|
100
|
+
breaks frames towards the host."""
|
|
101
|
+
b = self.ep.duplex_rate(self.index)
|
|
102
|
+
if b is None or size < b.min_size:
|
|
103
|
+
return False
|
|
104
|
+
big = [k for k, f in enumerate(self.frames) if len(f) >= b.min_size]
|
|
105
|
+
if not big:
|
|
106
|
+
return False
|
|
107
|
+
k = big[-1] # the answer on the line now (the latest); never spoiled twice
|
|
108
|
+
if k not in self.spoiled and self.ep.breaks(self.index, len(self.frames[k]), to_host=True, duplex=True):
|
|
109
|
+
self.frames[k] = _spoil(self.frames[k])
|
|
110
|
+
self.spoiled.add(k)
|
|
111
|
+
return True
|
|
112
|
+
|
|
93
113
|
def _gap(self, now: int) -> None:
|
|
94
114
|
if self.cand is not None and now - self.last_ms >= GAP_MS:
|
|
95
115
|
raw, self.cand = bytes(self.cand), None
|
|
@@ -109,9 +129,7 @@ class FakeSerialPort:
|
|
|
109
129
|
def _line(self, wire: bytes) -> bytes:
|
|
110
130
|
"""A framed message as the line delivers it at the port's rate now (`Endpoint.broken_rates`, `garble`)."""
|
|
111
131
|
if self.ep.breaks(self.index, len(wire), to_host=True):
|
|
112
|
-
|
|
113
|
-
body[-1] ^= 0xFF # the CRC's high byte
|
|
114
|
-
wire = b"\x00" + cobs.encode(bytes(body)) + b"\x00"
|
|
132
|
+
wire = _spoil(wire)
|
|
115
133
|
return self.garble(wire, self.ep.port_baud(self.index)) if self.garble else wire
|
|
116
134
|
|
|
117
135
|
def output(self, room: int = 4096) -> bytes:
|
|
@@ -119,6 +137,7 @@ class FakeSerialPort:
|
|
|
119
137
|
out = bytearray()
|
|
120
138
|
while self.frames and len(out) + len(self.frames[0]) <= max(room, len(self.frames[0])):
|
|
121
139
|
out += self.frames.pop(0)
|
|
140
|
+
self.spoiled = {k - 1 for k in self.spoiled if k}
|
|
122
141
|
if len(out) >= room:
|
|
123
142
|
return bytes(out)
|
|
124
143
|
while len(out) < room:
|
|
@@ -129,6 +148,16 @@ class FakeSerialPort:
|
|
|
129
148
|
return bytes(out)
|
|
130
149
|
|
|
131
150
|
|
|
151
|
+
def _spoil(wire: bytes) -> bytes:
|
|
152
|
+
"""A framed message with its CRC's high byte spoiled (a frame the line broke); bytes that are no frame as they are."""
|
|
153
|
+
try:
|
|
154
|
+
body = bytearray(cobs.decode(wire[1:-1]))
|
|
155
|
+
except cobs.CorruptFrame:
|
|
156
|
+
return wire
|
|
157
|
+
body[-1] ^= 0xFF
|
|
158
|
+
return b"\x00" + cobs.encode(bytes(body)) + b"\x00"
|
|
159
|
+
|
|
160
|
+
|
|
132
161
|
def _garble(data: bytes) -> bytes:
|
|
133
162
|
"""Bytes sent at one rate and read at another: a different byte for every byte, 0x00s among them (so the reader
|
|
134
163
|
sees broken candidates, not silence)."""
|
|
@@ -30,9 +30,10 @@ Options:
|
|
|
30
30
|
"DUT RX" labels when the profile has them, else the first free channels); configure then works
|
|
31
31
|
--uart-rx TEXT what arrives on that UART's RX every --every ms once it is configured (%d = a counter)
|
|
32
32
|
--no-port-speed the profile's port_speed (core §3.5; esp32-v003 has it) off: the op is unknown_operation
|
|
33
|
-
--broken-rate SPEC port_speed's line model: frames at RATE break (repeatable). SPEC is
|
|
34
|
-
only frames of MIN_SIZE bytes and more on the wire
|
|
35
|
-
host (in) or the probe (out) (default both)
|
|
33
|
+
--broken-rate SPEC port_speed's line model: frames at RATE break (repeatable). SPEC is
|
|
34
|
+
RATE[:MIN_SIZE][:in|out][:duplex][:everyN]: only frames of MIN_SIZE bytes and more on the wire
|
|
35
|
+
(default every frame), only towards the host (in) or the probe (out) (default both), only
|
|
36
|
+
while both ways carry such frames at once (duplex), only every Nth of them (everyN). The fake cannot see the host's own rate (a pty,
|
|
36
37
|
TCP): only the probe's rate decides
|
|
37
38
|
--run-hook SPEC what riscv-dm run does on every target: SPEC is module:function or path/file.py:function,
|
|
38
39
|
called as function(target, pc, regs) -> (stopped, dpc, elapsed_us). `target` is the
|
|
@@ -83,7 +84,9 @@ def build(a: argparse.Namespace) -> endpoint.Endpoint:
|
|
|
83
84
|
rate, *rest = spec.split(":")
|
|
84
85
|
size = next((int(x) for x in rest if x.isdigit()), 0)
|
|
85
86
|
way = next((x for x in rest if x in ("in", "out")), None)
|
|
86
|
-
|
|
87
|
+
every = next((int(x[5:]) for x in rest if x.startswith("every") and x[5:].isdigit()), 1)
|
|
88
|
+
ep.broken_rates[int(rate)] = endpoint.BrokenRate(size, to_host=way != "out", to_probe=way != "in",
|
|
89
|
+
duplex="duplex" in rest, every=every)
|
|
87
90
|
for n in a.absent:
|
|
88
91
|
ep.targets[(wire, ep.pairs[wire][n])].present = False
|
|
89
92
|
items = []
|
|
@@ -15,6 +15,7 @@ opens again (host guide §2.5).
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import contextlib
|
|
18
19
|
import random
|
|
19
20
|
import struct
|
|
20
21
|
from dataclasses import dataclass, field
|
|
@@ -194,14 +195,32 @@ class Host:
|
|
|
194
195
|
_revisions: dict = field(default_factory=dict) # fn -> interface revision from list
|
|
195
196
|
_describes: dict = field(default_factory=dict) # fn -> its describe TLVs (declarations: valid for one boot_id)
|
|
196
197
|
_boot_id: int | None = None
|
|
198
|
+
expect_ms: int = 0 # how long the request going out now may take on the probe (`expecting`)
|
|
199
|
+
# Called before a request takes its corr (the link's keepalive at a raised port_speed rate): what it sends first
|
|
200
|
+
# must carry the lower corr, or the probe takes the request for an old one (core §4.1)
|
|
201
|
+
before_request: Callable[[], None] | None = None
|
|
202
|
+
|
|
203
|
+
@contextlib.contextmanager
|
|
204
|
+
def expecting(self, ms: int):
|
|
205
|
+
"""Requests sent inside take up to `ms` on the probe (a run's timeout_ms, a dmi list's waits, an attach's
|
|
206
|
+
hold_ms, a capture's blocking, a save; core §6.1: the probe does not count the lease meanwhile). The link waits
|
|
207
|
+
at least that and a margin for each answer - also at a raised port_speed rate, where an ordinary request waits
|
|
208
|
+
a quarter of the lease. Nested: the longest holds."""
|
|
209
|
+
saved = self.expect_ms
|
|
210
|
+
self.expect_ms = max(saved, int(ms or 0))
|
|
211
|
+
try:
|
|
212
|
+
yield
|
|
213
|
+
finally:
|
|
214
|
+
self.expect_ms = saved
|
|
197
215
|
|
|
198
216
|
def next_corr(self) -> int:
|
|
199
217
|
self._corr = self._corr % 0xFFFF + 1
|
|
200
218
|
return self._corr
|
|
201
219
|
|
|
202
|
-
def call(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
220
|
+
def call(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
203
221
|
"""request() that also raises Failed unless the probe says it worked: what every operation wants."""
|
|
204
|
-
|
|
222
|
+
with self.expecting(expect_ms):
|
|
223
|
+
r = self.request(fn, op, payload, locked=locked)
|
|
205
224
|
if not r.succeeded:
|
|
206
225
|
raise Failed(r)
|
|
207
226
|
return r
|
|
@@ -212,11 +231,15 @@ class Host:
|
|
|
212
231
|
self.require_v1()
|
|
213
232
|
return self.session
|
|
214
233
|
|
|
215
|
-
def request(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
234
|
+
def request(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
216
235
|
"""locked=True sends the session id (role 0x81) once a session is open; lock-free requests may leave it off.
|
|
217
|
-
|
|
236
|
+
expect_ms: how long this request may take on the probe (`expecting`). Rejections raise; any other answer is
|
|
237
|
+
returned (Result.succeeded / .ran say what it was)."""
|
|
238
|
+
if self.before_request is not None:
|
|
239
|
+
self.before_request()
|
|
218
240
|
req = m.Request(self.next_corr(), fn, op, payload, self._session_for(locked))
|
|
219
|
-
|
|
241
|
+
with self.expecting(expect_ms):
|
|
242
|
+
result = m.Result.unpack(self.send(req.pack()))
|
|
220
243
|
if result.corr != req.corr:
|
|
221
244
|
raise ProtocolError(f"result for correlation {result.corr}, expected {req.corr}")
|
|
222
245
|
if result.resolution == m.REJECTED:
|
|
@@ -253,6 +276,8 @@ class Host:
|
|
|
253
276
|
"""Several requests in flight (`exchange` keeps the probe's in-flight and window limits); results in
|
|
254
277
|
order, rejects NOT raised - the caller looks at each result. Without `exchange`, one at a time."""
|
|
255
278
|
session = self._session_for(locked)
|
|
279
|
+
if self.before_request is not None:
|
|
280
|
+
self.before_request()
|
|
256
281
|
reqs = [m.Request(self.next_corr(), fn, op, payload, session) for fn, op, payload in requests]
|
|
257
282
|
packed = [r.pack() for r in reqs]
|
|
258
283
|
exchange = exchange or self.exchange
|