Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,14 @@ if(DEVOURER_MT7612U)
src/mt7612u/phy.cpp
src/mt7612u/radiotap.cpp
src/mt7612u/rx.cpp
src/mt7612u/station.cpp
src/mt7612u/tx.cpp
src/mt7612u/usb.cpp
src/mt7612u/Mt7612uRadio.cpp src/mt7612u/Mt7612uRadio.h
src/mt7612u/Mt7612uMapping.h
src/mt7612u/Mt7612uRxQueue.h
src/mt7612u/Mt7612uTsfRead.h
src/mt7612u/StationIdentity.h
src/mt7612u/internal.h
src/mt7612u/regs.h
src/mt7612u/initvals.h
Expand Down Expand Up @@ -1098,6 +1100,22 @@ target_include_directories(BssTableSelftest PRIVATE
target_compile_features(BssTableSelftest PRIVATE cxx_std_20)
add_test(NAME bss_table COMMAND BssTableSelftest)

# The MT7612U station-identity decision and the ownership hand-off
# (src/mt7612u/StationIdentity.h), in the style of
# tests/ack_responder_selftest.cpp. The policy is split from the register I/O
# so it can run here: its failure modes (a failed read that fails open, a
# failed read-back taken for a moved identity) are decisions, not writes, and
# are not visible in a register trace. Hardware coverage is
# `mt7612uprobe staid`. Header-only and pure, so it needs no link against the
# library.
if(DEVOURER_MT7612U)
add_executable(Mt7612uStationSelftest tests/mt7612u_station_selftest.cpp)
target_include_directories(Mt7612uStationSelftest PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/mt7612u)
target_compile_features(Mt7612uStationSelftest PRIVATE cxx_std_20)
add_test(NAME mt7612u_station_identity COMMAND Mt7612uStationSelftest)
endif()

# Headless guard for the TX quiesce seam (ITransport::quiesce_tx via
# RtlAdapter): the explicit "stop TX and wait it out" call every device makes
# before anything is released. UsbTransport's cancel/drain is validated on
Expand Down
2 changes: 1 addition & 1 deletion docs/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Emitters: L = library, RX/TX/... = demo. Optional fields in [brackets];
| ev | emitter | fields |
|---|---|---|
| `init.timing` | L (`src/InitTimer.h`) + demos | stage ("scope.stage", e.g. "demo.first_rx_frame", "txdemo.first_tx_submit"), ms, [xfers] (register transfers the stage spent on that adapter's transport, USB only — present on the Jaguar3 `j3hal.*` / `j3init.*` stages) |
| `adapter.caps` | RX, TX, doctor, txpower (`examples/common/caps_event.h`) | supported, chip, names, chip_id "0x..", gen, variant, transport, tx_chains, rx_chains, n_ss, stbc, ldpc, sgi, bw_max, bw[] (MHz), txpwr_max, txpwr_step_qdb, txpwr_step_measured, txpwr_min_qdb, txpwr_max_qdb, txpwr_rate_diffs, txpwr_rate_diffs_hw, txpwr_rate_diffs_measured, tune_2g4[]\|null, tune_5g[]\|null, char_2g4[]\|null, char_5g[]\|null, ldpc_rx_ht, ldpc_rx_vht, ldpc_rx_flag, vht_2g4, per_pkt_txpwr, per_pkt_txpwr_steps, per_pkt_txpwr_step_qdb, per_pkt_txpwr_min_qdb, per_pkt_txpwr_max_qdb, per_pkt_txpwr_measured, narrowband, fastretune, ack_responder, tx_retry_limit, he_er_su, per_chain_rssi, hw_rx_tsf, hw_beacon_txtsf, tsf_write, xtal_cap_max, xtal_cap_default |
| `adapter.caps` | RX, TX, doctor, txpower (`examples/common/caps_event.h`) | supported, chip, names, chip_id "0x..", gen, variant, transport, tx_chains, rx_chains, n_ss, stbc, ldpc, sgi, bw_max, bw[] (MHz), txpwr_max, txpwr_step_qdb, txpwr_step_measured, txpwr_min_qdb, txpwr_max_qdb, txpwr_rate_diffs, txpwr_rate_diffs_hw, txpwr_rate_diffs_measured, tune_2g4[]\|null, tune_5g[]\|null, char_2g4[]\|null, char_5g[]\|null, ldpc_rx_ht, ldpc_rx_vht, ldpc_rx_flag, vht_2g4, per_pkt_txpwr, per_pkt_txpwr_steps, per_pkt_txpwr_step_qdb, per_pkt_txpwr_min_qdb, per_pkt_txpwr_max_qdb, per_pkt_txpwr_measured, narrowband, fastretune, ack_responder, station_mode, tx_retry_limit, he_er_su, per_chain_rssi, hw_rx_tsf, hw_beacon_txtsf, tsf_write, xtal_cap_max, xtal_cap_default |
| `debug.wreg` | L (`DEVOURER_LOG_WRITES`) | addr "0x0nnn", width, val "0x…" |
| `hop.prof` | L (`DEVOURER_HOP_PROF`) | gen, ch, `<stage>_us`…, total_us |
| `tx.fail` | L (send failure; regress.py keys on it) | {status, actual_len, timeout} or {rc, timeout} |
Expand Down
302 changes: 302 additions & 0 deletions docs/mt7612u-station-identity.md

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions docs/mt7612u.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,11 @@ and nothing was ever queued on the healthy path.

## Offline tests

`ctest` runs seven MediaTek cells. The first four are C++ and need neither
`ctest` runs eight MediaTek cells. The first four are C++ and need neither
hardware nor the `DEVOURER_MT7612U` option — the code they cover is
header-only. The fifth is C++ and needs the option, because it calls the
subtree's own symbols. The last two are Python and need the `reference/mt76`
submodule:
subtree's own symbols. The sixth is header-only C++ gated on the option. The
last two are Python and need the `reference/mt76` submodule:

| cell | what it holds |
|---|---|
Expand All @@ -393,6 +393,7 @@ submodule:
| `mt7612u_rx_queue` | the RX hand-off queue's two load-bearing properties: a full ring drops the **newest** frame and counts it, and a popped slot outlives the queue lock. Broken, the first reorders frames or wedges the part and the second is a use-after-free — and both look like a healthy link until a packet processor falls behind |
| `mt7612u_tsf_read` | that the TSF read stays coherent when the low word wraps between any two of its register accesses: a scripted counter swept across the wrap one microsecond at a time, every failure position, and an all-ones low word as a value. The pre-fix DW0,DW1 order runs against the same sweep and must tear, or the cell cannot see the bug. Mutation-tested by hand three ways — no retry, a retry that keeps the first high word, a retry that skips the low-word re-read — each fails |
| `mt7612u_tsf_api` | that a failed TSF read stays distinguishable from a value at the C entry points: `mt7612u_read_tsf_chk` refuses a NULL device or output with -1 and does not write through the pointer, and `mt7612u_read_tsf` answers 0. `0xffffffff` is a legitimate register word here, so only the return code can carry a failure. Not free: deleting the guard in `mt7612u_read_tsf_chk` segfaults the cell (hand-run). Reaches the NULL refusals only; a failed transfer on a live device, the `Mt7612uRadio::ReadTsf` throw and the `tsf_write` caps bit need the part (`bringup caps` prints it, and `Mt7612uRadio::GetAdapterCaps` takes `tsf_write_ok` from it rather than restating it) |
| `mt7612u_station_identity` | the station-identity decision and ownership hand-off (`src/mt7612u/StationIdentity.h`): both register reads fail closed, a failed port read is reported as a failed read rather than a mismatch, argument checks run before any I/O, the port comparison is tri-state (a failed read is UNKNOWN, never DIFFERENT), a verified move drops the arm, an unreadable one keeps it, and a failed start that unwinds restores it. Header-only, gated on `DEVOURER_MT7612U`, which the MediaTek CI jobs set. The hardware counterpart is `mt7612uprobe staid` (`docs/mt7612u-station-identity.md`) |
| `mt7612u_usb_ids_vs_mt76` | that the id table above really is the complete `mt76x2u_device_table` from the pinned reference, byte-checked. An id we *forgot* is invisible to `mt7612u_usb_ids` — the adapter just falls through to the Realtek path — and the first draft of that header had 11 of the 16, taken from the host's kernel tree |
| `mt7612u_initvals_generated` | that the checked-in `initvals.h` still matches what `tools/extract_mt7612u_tables.py` produces from the pinned reference |

Expand Down Expand Up @@ -420,7 +421,7 @@ nothing. Each fails the cell, and each names the property it broke.

| test | what it holds |
|---|---|
| `api_link` | takes the address of all 36 public entry points while including only the public header, so a declaration that loses its definition is a link error. Still compiled as C, which is what keeps the `extern "C"` guard honest now the library itself is C++ |
| `api_link` | takes the address of all 39 public entry points while including only the public header, so a declaration that loses its definition is a link error. Still compiled as C, which is what keeps the `extern "C"` guard honest now the library itself is C++ |
| `frame_shape` | `mt_hdrlen_from_fc()` over management, all eight control subtypes and the five data shapes; the RX L2-pad fold on a synthetic QoS frame, with a negative control that redoes the old fixed-24 fold and asserts the QoS Control really is destroyed; the radiotap VHT bandwidth mapping over all eleven codes the part can express; the txwi pktid for a normal send (0), `MT_TXOPT_TXS` alone and `MT_TXOPT_PKTID(id)` |
| `field_macros` | `MT_CTZ` against `__builtin_ctz` over all 32 single-bit and all 528 contiguous masks, plus a `FIELD_PREP`/`FIELD_GET` round-trip, plus a static initialiser that fails to compile if the macro stops being constant-foldable, plus the `MT_TX_RETRY_CFG` read-modify-write (both limits set, bits 16+ kept) |
| `log_sink` | that `mt7612u_set_log_sink()` **diverts** rather than copies — stderr must stay silent while a sink is installed — that the sink gets the bare message with no prefix to double up, that every level letter arrives, and that NULL restores the built-in sink instead of silencing the library |
Expand Down Expand Up @@ -580,13 +581,14 @@ Stated because the numbers above are uniformly favourable.
firmware-running bit.
- **The 48 ms fast retune is our implementation, not the floor.** The floor is
unmeasured.
- **Five ctest cells run in CI, two more only on a bench, and the subtree's own
- **Six ctest cells run in CI, two more only on a bench, and the subtree's own
four still run nowhere automatically.** With `DEVOURER_MT7612U=ON` the whole
platform matrix (gcc, clang, MSVC, mingw, macOS) builds the subtree and the
sanitizer job links it, so a portability or lifetime regression is caught.
`mt7612u_usb_ids`, `mt7612u_mapping`, `mt7612u_rx_queue` and
`mt7612u_tsf_read` run on every configuration, and `mt7612u_tsf_api`
wherever the option is on, which the MediaTek CI jobs set. The TSF pair and
`mt7612u_tsf_read` run on every configuration, and `mt7612u_tsf_api` and
`mt7612u_station_identity` wherever the option is on, which the MediaTek CI
jobs set. The TSF pair and
the queue cover backend *behaviour* rather than a lookup, and nothing else
does. Bring-up,
the teardown ordering, the 1 Hz tick and TX still have no automated coverage,
Expand Down
1 change: 1 addition & 0 deletions examples/common/caps_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ inline void emit_adapter_caps(EventSink &sink, IRadio *dev) {
.f("narrowband", c.narrowband_ok ? 1 : 0)
.f("fastretune", c.fastretune_ok ? 1 : 0)
.f("ack_responder", c.ack_responder_ok ? 1 : 0)
.f("station_mode", c.station_mode_ok ? 1 : 0)
.f("tx_retry_limit", c.tx_retry_limit_ok ? 1 : 0)
.f("he_er_su", c.he_er_su_ok ? 1 : 0)
.f("per_chain_rssi", c.per_chain_rssi ? 1 : 0)
Expand Down
49 changes: 49 additions & 0 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,55 @@ struct AdapterCaps {
bool ack_responder_ok = false;
bool tx_retry_limit_ok = false;

/* station_mode_ok: IRadio::SetStationIdentity can program this MAC for the
* STATION half of an infrastructure BSS, and the behaviour a station needs
* from the silicon has been measured on air. Gate station-mode callers on
* this rather than on SetStationIdentity's return value alone, so a caller
* can refuse before it starts a handshake it cannot finish.
*
* False means "not ported / not measured", never "the silicon cannot". Do
* not set it from a code-reading: the bar is an on-air cell showing this
* adapter receiving unicast addressed to it and being ACKed for what it
* sends - the same shape of evidence ack_responder_ok carries, measured per
* die. (The MT7612U acknowledgement cells use a raw injector and an armed
* ACK responder as the peer, not an AP.)
*
* TRUE on MT7612U, and read docs/mt7612u-station-identity.md - its
* retraction section first - before quoting a number from it. Both halves
* of the bar are measured there, with controls: a Realtek peer's own CCX
* reports show this MAC acknowledging 100% of unicast addressed to it with
* nothing armed (0.45 mean retries, 1279 reports) against three controls
* pinned at the peer's 12-retry limit (a destination nobody holds, the DUT
* absent, and MT_AUTO_RSP_EN cleared); and the MAC's own TX status FIFO
* shows its uplink acknowledged 200/200 at 0.0 mean retries against a
* 0/200 control run to the full ladder. The uplink cell sent from the
* bring-up tool with an ACK-requesting TXWI and a retry limit of 15 - not
* a library session, whose defaults (NOACK stream radiotap, tx.retry_limit
* 0) send each unicast once; see IRadio::SetStationIdentity. Note the
* limits the measurements do NOT clear, which a caller should know:
*
* - every cell ran an UNASSOCIATED station receiving traffic it had not
* negotiated, so power save, TIM parsing, cross-BSS duplicate detection
* and hardware key lookup are untested;
* - those cells did not drive SetStationIdentity itself. On this part the
* seam writes no register, so the measured hardware state is the state
* a successful arm leaves behind, but the literal "arm through IRadio,
* then measure" path is not what the cells ran;
* - the cells ran the MANAGED receive filter, and the library's own RX
* path does not: Mt7612uRadio::StartRxLoop calls
* mt7612u_set_monitor_rx() unconditionally, so a station driven through
* IRadio runs PROMISCUOUS. Acknowledgement does not depend on it (a
* monitor-filter run of the same auto-ACK cell also read 100%), but the
* "moving the port identity makes a station deaf" half of the rationale
* is specific to the managed filter;
* - two units, one peer model, one channel, near field, no soak; the
* second unit reproduced the acknowledgement and uplink cells (its
* uplink at 1.9 mean retries against the first unit's 0.0), not the
* BSSID receive table.
*
* FALSE on every other backend: not ported. */
bool station_mode_ok = false;

/* --- feature flags --- */
/* Per-packet TX power: a per-frame power trim driven by radiotap
* DBM_TX_POWER (dB delta vs the calibrated table / session base) or a
Expand Down
Loading
Loading