Skip to content

40 MHz broken in both directions on Jaguar1: TX not decodable, RX deaf even to 20 MHz frames #352

Description

@josephnef

Summary

40 MHz does not work on Jaguar1 in either direction. Both paths fail
independently, each proven against the vendor kernel driver on the other end.

This survived the receiver-qualification cleanup in #351 — it is not a
link-margin artifact like the #348 reports were.

Measurements

tests/bw40_isolate.sh (added with this report). One end is always the vendor
kernel driver, so each leg tests exactly one devourer path. Every leg carries its
own control, because the easiest way to fake this bug is a monitor that is simply
deaf in HT40 mode.

DUT: RTL8812AU (0bda:8812), devourer side. Vendor side: Comfast CF-924AC V2
(RTL8822BU, external antennas) under reference/rtl88x2bu. ch6, HT40+.

TX leg — devourer transmits, vendor driver receives:

monitor devourer TX sent vendor decoded
20 MHz MCS0/20 5934 4456
40 MHz MCS0/40 5949 0
40 MHz MCS0/20 5884 3059 ← control: the HT40 monitor works

The third row is the one that matters: the same HT40 monitor that decodes 3059
frames of 20 MHz traffic decodes zero of devourer's 40 MHz traffic. So
devourer's 40 MHz TX does not produce a frame this receiver can decode.

RX leg — vendor driver transmits, devourer receives:

devourer RX injected devourer decoded
20 MHz 875 112 ← control
40 MHz (DEVOURER_BW=40) 752 0

The injector sends a 20 MHz frame on the primary channel in both cells. So
at DEVOURER_BW=40 the receiver cannot hear even a 20 MHz frame on its own
primary — it is not a wide-frame decode problem, the receiver goes broadly deaf.

Scope

  • Reproduced on Jaguar1 (RTL8812AU). Earlier ad-hoc runs saw the same RX
    behaviour on Jaguar2 (8822BU) at both primary-channel offsets and in both role
    directions; that needs re-running with this harness now that it has controls.
  • Both DEVOURER_CHOFFSET/DEVOURER_HOP_OFFSET values were swept in the
    earlier runs with no difference.
  • 80 MHz is untested here.

Why this was not caught before

The existing 40 MHz TX validation (tests/jaguar2_tx_bw40.sh) uses a kernel
sniffer and covers Jaguar2. The devourer 40 MHz receive path appears never to
have been exercised on air at all, and the Jaguar1 40 MHz transmit path is not
covered by that script.

docs/vht-on-2g4.md currently describes this as a receiver-side gap. That is
now known to be only half of it — the TX side fails too — and the doc should be
corrected alongside a fix.

Impact

40 MHz is the headline bandwidth for the 2×2 rates (VHT2SS_MCS9/40 = 400 Mbps
PHY, the whole point of #331). None of it is reachable today, and a caller that
sets DEVOURER_BW=40 / DEVOURER_HOP_BW=40 gets silence with no error — the
transmitter reports every frame submitted and the receiver reports a clean
bring-up.

Reproducing

sudo REGRESS_VBUS_MAP="0bda:8812=3-2.3.4,3;0bda:b812=4-2.3,3" \
     tests/bw40_isolate.sh

Read the TX leg's third cell first — if the HT40 monitor is deaf there, the
whole leg is void. The RX leg self-voids when its own 20 MHz control fails.


🤖 Filed with Claude Code

Activity

  1. josephnef commented on Jul 28, 2026

    @josephnef
    CollaboratorAuthor

    Register diff: one surviving lead, BB 0x8b0

    tests/bw40_regdiff.sh (pushed on rx-bw40-gap) puts the same adapter into each width under each driver and diffs the canary set, reporting only registers that differ at 40 MHz and match at 20 MHz.

    register devourer @40 vendor @40 both @20
    BB 0x8b0 0x00000642 0x00000618 0x00000642
    BB 0xe90 0x01807406 0x01807408 0x01807C06

    The base BB table loads 0x8b0 = 0x00000600 on both sides, so something writes the low bits at runtime and the vendor moves to 0x618 for 40 MHz where devourer stays at 0x642. Not yet chased to a cause — recording it rather than guessing.

    Two things ruled out

    • Not the 40 MHz register sequence. devourer's phy_PostSetBwMode8812 40 MHz branch is register-for-register identical to the vendor's (0x8ac, 0x8c4, 0x483 DATA_SC, 0x848 L1PeakTH, 0x84a CCK_System, RF 0x18[11:10]).
    • Not a missing phy_set_mac_clk_8812a. That function takes the same branch for 20 and 40 MHz (only 10 MHz differs), so a missing call cannot explain 20-works/40-fails.

    Harness correction (affects anyone using the canary tooling)

    tools/canary_kernel_dump.sh sets the channel itself, at 20 MHz, silently overwriting a caller that had already selected HT40 — so it would describe a 20 MHz chip while claiming to be a 40 MHz capture. It now takes an optional HT-mode argument. The diff caught this on itself: the vendor column read 0x8ac bits[1:0]=00 at "40 MHz".

    This does not affect the TX/RX isolation in the issue body — bw40_isolate.sh sets HT40 directly and never calls the dump tool. Independently confirmed the vendor driver does honour HT40 in monitor mode (0x8ac = 0x0FF0FA09, bits[1:0]=01).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions