Skip to content

mt7612u: cold boot, dwell-cadence polling and software-CCMP cost, measured on the first bench unit - #445

Closed
josephnef wants to merge 4 commits into
mt7612u-retune-racesfrom
mt7612u-bench-cf922ac
Closed

josephnef wants to merge 4 commits into
mt7612u-retune-racesfrom
mt7612u-bench-cf922ac

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Stacked on #444 (merge that first; this rebases to its own commit).

What

The bench work the first lab MT7612U (Comfast CF-922AC, INVENTORY slot #17) made possible, with the tooling each item needed:

  • Cold boot (docs/mt7612u.md open item): tests/adapter_doctor_cold.sh gains DOCTOR_DUT_PID / DOCTOR_MT7612U_FW_DIR, and build/doctor a --mt7612u-fw-dir flag (the doctor reads no environment; this backend's firmware is not embedded). 3/3 true-VBUS-cold reps HEALTHY, 4200 / 2585 / 4175 frames in the 8 s smoke.
  • Follow-ups left open by the CCX / neutral-sensing work (#431, #432) #433 §1, poller vs tick: rxdemo gains DEVOURER_RX_BUSY_MS (vendor-neutral arm/wait/read, one rx.busy per window — the survey executor's dwell shape) and the backend a measurement-only DEVOURER_MT7612U_PHY_TICK=0 control. Four arms × 2 reps × 30 s against a 700 fps MCS7 flooder: all inside run-to-run spread, ~80% busy read throughout. Limit stated in the subtree guide: the no-tick control does not collapse at 700 fps, so the ~3000 fps regime the tick's benchmark is about was not reached — nothing on this bench injects that fast.
  • MT7612U: hardware CCMP is unreached — what it would take, and why not to design the key surface yet #425, software CCMP cost: tests/ccmp_cost_bench.sh (headless) — 1.51 µs per 1500-byte frame vs 2.4 ms per frame on this transport's send path. Recorded in docs/mt7612u-ap-mode.md.

Doc updates in docs/adapter-doctor.md, tests/README.md, root CLAUDE.md (the new demo knob) and src/mt7612u/CLAUDE.md. ctest 69/69.

🤖 Generated with Claude Code

…sured on the first bench unit

Three open items the MediaTek backend carried for want of an MT7612U on the
bench, worked on a Comfast CF-922AC, plus the tooling each one needed:

- Cold boot. tests/adapter_doctor_cold.sh takes DOCTOR_DUT_PID and
  DOCTOR_MT7612U_FW_DIR, and build/doctor a --mt7612u-fw-dir flag (it reads
  no environment by design; this backend's firmware is not embedded). Three
  true-VBUS-cold reps with mt76x2u blacklisted and a verified flood: HEALTHY
  on each, 4200 / 2585 / 4175 frames in the 8 s smoke.

- Does polling the channel timers at dwell cadence disturb the 1 Hz PHY
  tick's gain tracking (#433 §1)? rxdemo gains DEVOURER_RX_BUSY_MS, a
  vendor-neutral arm/wait/read poller emitting rx.busy, and DeviceConfig
  mt7612u.phy_tick (DEVOURER_MT7612U_PHY_TICK=0) as the measurement-only
  control arm. Four arms x 2 reps x 30 s against a 700 fps MCS7 flooder:
  no measurable cost, ~80% busy read throughout. The no-tick control does
  not collapse at that load, so the regime the tick's benchmark is about
  (~3000 fps peer) was not reached; no injector on this bench airs that
  fast, and the subtree guide says so.

- What software CCMP costs (#425): tests/ccmp_cost_bench.sh times the
  harness's OpenSSL AES-128-CCM call — 1.51 us per 1500-byte frame on the
  bench host, against 2.4 ms per frame on this transport's send path.
  Recorded in docs/mt7612u-ap-mode.md; GTK rekey, not throughput, is the
  case for hardware keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add MT7612U cold-boot, dwell polling, and CCMP benchmarks

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Adds MT7612U cold-boot support and records three healthy hardware repetitions.
• Adds vendor-neutral dwell polling and a measurement-only PHY tick control.
• Benchmarks software CCMP cost and documents measured performance limits.
Diagram

graph TD
  Cold["Cold-boot script"] --> Doctor["Adapter doctor"] --> Config["Device config"] --> Radio["MT7612U radio"] --> Docs["Bench findings"]
  Rx["RX busy poller"] --> Config
  Rx --> Busy["Busy airtime API"] --> Radio
  Bench["CCMP benchmark"] --> Docs
Loading
High-Level Assessment

The current approach is appropriate: it adds narrowly scoped measurement controls without weakening production defaults, uses the existing vendor-neutral busy-airtime interface, and keeps machine-dependent performance measurements outside automated correctness tests. Embedding firmware or making the PHY tick disabled by default would increase packaging or operational risk without improving the bench evidence.

Files changed (14) +244 / -15

Enhancement (3) +67 / -1
main.cppAccept an MT7612U firmware directory +10/-0

Accept an MT7612U firmware directory

• Adds --mt7612u-fw-dir argument parsing and passes the selected directory into DeviceConfig. This enables doctor bring-up when MediaTek firmware is not embedded.

examples/doctor/main.cpp

main.cppAdd dwell-cadence channel-busy event polling +52/-0

Add dwell-cadence channel-busy event polling

• Adds DEVOURER_RX_BUSY_MS and a vendor-neutral worker that emits one rx.busy event per measurement window. The worker supports interruptible waits and is joined during shutdown.

examples/rx/main.cpp

Mt7612uRadio.cppMake PHY tick startup configurable +5/-1

Make PHY tick startup configurable

• Starts the periodic MT7612U PHY tick only when enabled in DeviceConfig. Emits a prominent warning when the measurement-only control disables it.

src/mt7612u/Mt7612uRadio.cpp

Tests (3) +88 / -2
adapter_doctor_cold.shSupport non-Realtek DUTs in cold-boot testing +11/-2

Support non-Realtek DUTs in cold-boot testing

• Adds optional DUT PID and MT7612U firmware-directory environment controls. The script forwards these values to doctor while retaining existing Realtek defaults.

tests/adapter_doctor_cold.sh

ccmp_cost_bench.cppAdd a software CCMP microbenchmark +67/-0

Add a software CCMP microbenchmark

• Measures the AP harness's OpenSSL AES-128-CCM encryption sequence across five frame sizes. It emits JSON timing, throughput, and frame-rate results after warm-up.

tests/ccmp_cost_bench.cpp

ccmp_cost_bench.shAdd a headless CCMP benchmark runner +10/-0

Add a headless CCMP benchmark runner

• Compiles the temporary benchmark binary with OpenSSL, reports CPU context, runs the requested iteration count, and cleans up afterward.

tests/ccmp_cost_bench.sh

Documentation (6) +78 / -12
CLAUDE.mdDocument the vendor-neutral RX busy polling control +6/-0

Document the vendor-neutral RX busy polling control

• Documents DEVOURER_RX_BUSY_MS and explains how its arm/wait/read cadence models survey dwell polling across supported backends.

CLAUDE.md

adapter-doctor.mdDocument MediaTek cold-boot doctor configuration +10/-0

Document MediaTek cold-boot doctor configuration

• Adds the MT7612U VID, PID, module blacklist, and firmware-directory requirements. It also explains which doctor health signals are unavailable for this backend.

docs/adapter-doctor.md

mt7612u-ap-mode.mdRecord measured software CCMP encryption cost +14/-0

Record measured software CCMP encryption cost

• Reports AES-128-CCM timings by frame size and compares them with MT7612U transport costs. It concludes that key lifecycle support, rather than throughput, is the primary hardware-key benefit.

docs/mt7612u-ap-mode.md

mt7612u.mdRecord successful MT7612U cold-boot validation +20/-5

Record successful MT7612U cold-boot validation

• Documents three true-VBUS cold-boot repetitions and their RX smoke results. Removes cold-boot validation from the open-item list and renumbers the remaining work.

docs/mt7612u.md

CLAUDE.mdDocument dwell-polling bench results and limits +16/-6

Document dwell-polling bench results and limits

• Records four polling and PHY-tick control arms at approximately 700 frames per second. It explicitly limits the conclusion because the bench could not reproduce the faster collapse regime.

src/mt7612u/CLAUDE.md

README.mdDocument MT7612U doctor and CCMP benchmark usage +12/-1

Document MT7612U doctor and CCMP benchmark usage

• Adds MediaTek cold-boot environment and firmware instructions. It also describes the headless CCMP cost benchmark and how to interpret its host-specific results.

tests/README.md

Other (2) +11 / -0
env_config.cppLoad the MT7612U PHY tick measurement control +2/-0

Load the MT7612U PHY tick measurement control

• Maps DEVOURER_MT7612U_PHY_TICK into DeviceConfig while preserving the enabled default unless explicitly set to zero.

examples/common/env_config.cpp

DeviceConfig.hAdd the MT7612U PHY tick configuration flag +9/-0

Add the MT7612U PHY tick configuration flag

• Introduces an enabled-by-default phy_tick option for controlled receiver measurements. Documentation warns that disabling the tick is unsuitable for normal operation.

src/DeviceConfig.h

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 23, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Software encryption cost is understated ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
ccmp_cost_bench creates one EVP_CIPHER_CTX outside all timed operations, while the AP harness's
ccm allocates and frees a context for every encryption or decryption call. Because every real
frame pays that lifecycle cost, the documented timings and derived per-frame percentages omit work
from the source-tree software path and cannot substantiate the stated throughput or transport-budget
comparison.
Code

tests/ccmp_cost_bench.cpp[R47-48]

+  EVP_CIPHER_CTX *c = EVP_CIPHER_CTX_new();
+  for (int sz : sizes) {
Evidence
The benchmark allocates one cipher context before processing all sizes and timed iterations, whereas
the production-like AP helper allocates and frees its context inside every call. The documentation
nevertheless describes the benchmark as matching the harness, treats its lower result as the cost of
the current software path, and derives percentage and throughput conclusions from it, demonstrating
that the documented MT7612U hardware-key comparison is inconsistent with the source implementation.

Keep MT7612U Hardware-Key Documentation Current
tests/ccmp_cost_bench.cpp[47-65]
tests/ap_wpa2.cpp[228-250]
docs/mt7612u-ap-mode.md[169-181]
tests/ccmp_cost_bench.cpp[20-65]
tests/ap_wpa2.cpp[218-246]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The software CCMP benchmark reuses one OpenSSL cipher context, but the AP harness allocates and frees a context for every encryption or decryption operation, so the documented per-frame measurements omit work performed by the current implementation.
## Fix Focus Areas
- tests/ccmp_cost_bench.cpp[20-65]
- tests/ap_wpa2.cpp[218-250]
- docs/mt7612u-ap-mode.md[169-181]
## Recommended Fix
Make each timed benchmark iteration reproduce the AP helper's complete context allocation, encryption or decryption, and cleanup lifecycle. Report encryption and decryption separately if both are discussed, rerun the corrected benchmark, and replace the documented timings, derived percentages, and related throughput or transport-budget conclusions with the resulting measurements.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Busy polling races radio controls 🐞 Bug ☼ Reliability
Description
main invokes ArmChannelBusy() and GetChannelBusy() from busy_emitter, although those APIs
require the same single control thread used for initialization and retuning. The poller starts
before the eventual Init() call and remains active during sweep retunes and energy reads, allowing
control-plane register operations to overlap and invalidate the emitted measurements.
Code

examples/rx/main.cpp[R1577-1582]

+    busy_emitter = std::thread([&busy_emitter_stop, dev]() {
+      /* First window after bring-up settles, like the other pollers. */
+      for (uint32_t s = 0; s < 1000 && !busy_emitter_stop.load(); s += 50)
+        std::this_thread::sleep_for(std::chrono::milliseconds(50));
+      while (!busy_emitter_stop.load()) {
+        const uint32_t armed = dev->ArmChannelBusy(g_rx_busy_ms * 1000u);
Evidence
The interface explicitly restricts busy-window operations to one control thread. The new emitter is
an independent thread created well before the normal-path Init(), while sweep mode later performs
channel changes and energy reads from other threads.

src/IRadio.h[600-647]
examples/rx/main.cpp[1570-1605]
examples/rx/main.cpp[2152-2210]
examples/rx/main.cpp[2291-2302]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The busy-airtime emitter calls control-plane APIs from a background thread despite their single-control-thread contract, allowing them to overlap initialization, retuning, and other sensing operations.
## Fix Focus Areas
- examples/rx/main.cpp[1570-1605]
- examples/rx/main.cpp[2152-2210]
- examples/rx/main.cpp[2291-2302]
## Recommended Fix
Run busy-airtime arm/read operations on the application's control thread and keep only non-radio waiting or event emission on a worker. Coordinate the poller with initialization and sweep modes so no other control-plane call can execute between an arm and its corresponding read.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Cold boot errors can look healthy ✓ Resolved 🐞 Bug ≡ Correctness
Description
adapter_doctor_cold.sh now requests the exact MediaTek PID after waiting only for any idProduct
node, although this device initially enumerates under its ZeroCD PID before changing to 0x7612. If
the doctor runs during that transition it returns 3 for no matching adapter, and the wrapper ignores
return values above 2 when calculating worst, allowing failed repetitions to leave the final exit
status at 0.
Code

tests/adapter_doctor_cold.sh[R114-116]

+  "$DOCTOR" --vid "$DUT_VID" ${DUT_PID:+--pid "$DUT_PID"} --bus "$BUS" --port "$PP" \
+    ${MT_FW:+--mt7612u-fw-dir "$MT_FW"} \
+    --channel "$CHANNEL" --expect-traffic > "$LOG/rep$i.log" 2>&1
Evidence
The new MediaTek path supplies an exact PID, while the readiness loop only checks whether
idProduct exists. The repository documents that this adapter first appears as 0e8d:2870, the
doctor returns 3 when exact lookup finds no adapter, and the wrapper's accumulator excludes every
status above 2.

tests/adapter_doctor_cold.sh[99-123]
examples/doctor/main.cpp[204-219]
docs/mt7612u.md[642-654]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new exact-PID doctor invocation can run while the MediaTek device still has its temporary ZeroCD identity, and the wrapper then discards the doctor's error status.
## Fix Focus Areas
- tests/adapter_doctor_cold.sh[99-119]
- examples/doctor/main.cpp[204-219]
## Recommended Fix
When `DOCTOR_DUT_PID` is set, wait until the sysfs `idProduct` value equals that PID rather than merely waiting for the file to exist, and fail on timeout. Treat doctor return codes above 2 as fatal or preserve them in the aggregate exit status instead of converting an all-error run to healthy.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Large busy windows yield wrong timing ✓ Resolved 🐞 Bug ≡ Correctness
Description
g_rx_busy_ms truncates strtoul() directly to uint32_t, and the poller then computes
microseconds with an unchecked 32-bit multiplication. Values above 4,294,967 milliseconds wrap the
armed duration while retaining the original sleep duration, and values near the 32-bit maximum can
also wrap the sleep-loop counter before it reaches the requested endpoint.
Code

examples/rx/main.cpp[R313-316]

+static const uint32_t g_rx_busy_ms = []() -> uint32_t {
+  const char *e = std::getenv("DEVOURER_RX_BUSY_MS");
+  return e ? static_cast<uint32_t>(std::strtoul(e, nullptr, 0)) : 0u;
+}();
Evidence
The parser casts an unrestricted unsigned long result to 32 bits, while the arm call multiplies
that value by 1000u and the wait loop advances a 32-bit counter. ArmChannelBusy(0) explicitly
means that no window was armed, so overflow can change the operation's semantics.

examples/rx/main.cpp[313-316]
examples/rx/main.cpp[1581-1589]
src/IRadio.h[643-647]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The busy-window environment value is truncated and multiplied without range checks, causing large or negative-looking inputs to produce wrapped timing values or nonterminating sampling loops.
## Fix Focus Areas
- examples/rx/main.cpp[313-316]
- examples/rx/main.cpp[1581-1586]
## Recommended Fix
Parse with `strtoull`, validate the complete input and reject negative, malformed, zero-disabling, or out-of-range values. Cap accepted milliseconds at `UINT32_MAX / 1000`, then use a remaining-duration sleep calculation that cannot wrap.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/ccmp_cost_bench.cpp Outdated
Comment thread examples/rx/main.cpp
Comment thread examples/rx/main.cpp
Comment thread tests/adapter_doctor_cold.sh
josephnef and others added 3 commits September 23, 2026 13:18
…tract, the cold wrapper waits for the DUT's PID, the CCMP bench times the whole call

- DEVOURER_RX_BUSY_MS: parsed with strtoull and capped so the microsecond
  window cannot wrap; refused alongside DEVOURER_RX_SWEEP / _HOP_CHANNELS
  (those retune from the main thread, and the busy window is a
  single-control-thread contract); the first arm waits for bring-up's first
  frame instead of racing Init(); the dwell sleep is remaining-duration.
- adapter_doctor_cold.sh: with DOCTOR_DUT_PID set, wait until sysfs
  idProduct IS that PID - a ZeroCD part enumerates under its installer-disk
  id first - and fail the run on timeout; a doctor rc above 2 is "did not
  run", fatal, never folded into a HEALTHY aggregate.
- ccmp_cost_bench: each timed call is the harness's ccm() whole, fresh
  context included, encrypt and decrypt separately. Re-measured: 1.52 /
  1.54 us per 1500-byte frame - the lifecycle is not where the time goes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Collaborator Author

Review addressed (b59b4f6), re-validated on the unit:

  • Busy polling vs the control-thread contract — the poller now refuses to run alongside DEVOURER_RX_SWEEP / DEVOURER_HOP_CHANNELS (exit 2, verified), and its first arm waits for bring-up's first frame instead of racing Init(). Outside those modes the main thread issues no control-plane call after bring-up, so arm/read on the worker is the only control caller. 20 s at 100 ms cadence: 177 windows, 177 valid, 0 spoiled.
  • Large windowsstrtoull, whole-number validation, capped at UINT32_MAX/1000, remaining-duration sleep.
  • Cold boot on a ZeroCD part — with DOCTOR_DUT_PID set the wrapper waits until sysfs idProduct is that PID (30 s, fatal on timeout), and a doctor rc above 2 aborts the run instead of averaging into HEALTHY. Two fresh VBUS-cold reps: HEALTHY / HEALTHY.
  • CCMP bench — each timed call is the harness's ccm() whole (fresh context, init, AAD, payload, tag, free), encrypt and decrypt separately: 1.52 / 1.54 µs per 1500-byte frame. Doc and MT7612U: hardware CCMP is unreached — what it would take, and why not to design the key surface yet #425 updated; the conclusion is unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant