Skip to content

RTL8821AU + RTL8814AU 5GHz TX/RX: phydm IQK calibration port required #59

Closed

Description

@josephnef

Problem

Devourer's 5GHz code path silently fails on multiple chip + direction combos. The 2026-05-29 + 2026-05-30 investigation traced the root cause to missing phydm IQK / DPK calibration. At 2.4GHz, the chip's reset-state calibration is tolerant enough for TX/RX; at 5GHz, the PA / LO mismatch tolerance is tighter and IQK is effectively mandatory.

Affected matrix cells (5GHz, ch100 / ch36)

TX RX Status
RTL8821AU devourer any kernel 0 hits on-air ✗
RTL8821AU devourer any devourer 0 hits ✗
any kernel RTL8821AU devourer 0 hits ✗
any kernel RTL8814AU devourer 0 hits ✗
any devourer RTL8814AU devourer 0 hits ✗

The same cells at 2.4GHz (ch6) all pass with hundreds-to-thousands of hits. Cross-reference: PR #58 added the band asymmetry documentation; PR bodies #34 / #42 / #49 record the 5GHz status as "still broken" because their matrix runs were captured at --channel 100.

The persistent RTL8814AU TX gate is a separate, deeper issue (broken on both bands) tracked elsewhere and NOT addressed by this port. See kaeru ref RTL8814AU libusb-userspace bulk-OUT does not produce on-air TX.

Root cause analysis

Verified 2026-05-30 against upstream aircrack-ng/rtl8812au-5.6.4.2_35491.20191025 (DKMS source on the devourer-testrig VM at /usr/src/8812au-5.6.4.2_35491.20191025/).

  1. Devourer's phy_SwChnl8812 matches upstream's production-chip code byte-for-byte. No missing channel-set helpers for RTL8821U normal-chip. Verified by line-by-line comparison of src/RadioManagementModule.cpp:834-887 vs hal/rtl8812a/rtl8812a_phycfg.c:1773-1864.

  2. The kernel-side RF[0x42] writes I captured during channel-set (ch6: data=0x37cf8; ch100: data=0x378f0; 4 writes each) come from inside the IQK code, NOT from phy_SwChnl8812. Specifically hal/phydm/halrf/rtl8812a/halrf_8812a_win.c:1989, 2678 (entry: do_iqk_8812a).

  3. Smoke-tested a hardcoded phy_set_rf_reg(RF_PATH_A, 0x42, 0xfffff, 0x378f0) at end of phy_SwChnl8812 — devourer 8821AU TX @ ch100 still produced 0 hits on-air. So the gate isn't one register; it's the full IQK sequence.

  4. Kernel's BB+RF write footprint per channel-set at ch100 (203 writes total): 0x0994 ×56, 0x08fc/0x08f8/0x09a4/0x0a2c/0x0b58/0x08a4 ×18 each, 0x0830/0x0834/0x08b0 ×9 each. This is the classic phydm IQK loop signature.

  5. Devourer has no IQK code at all. grep -rn IQK src/ returns only the _needIQK flag, no implementation.

Pickup checklist for the next person

1. Read the kaeru evidence first

The investigation captured detailed evidence and intermediate hypothesis tests in two episodes (search via mcp__kaeru with initiative: devourer):

  • 8821au-5ghz-tx-gate-investigation-started-2026-05-30 — initial wire-diff, smoke tests, dead-ends ruled out
  • 8821au-5ghz-tx-gate-iqk-port-required-2026-05-30 — final conclusion + port scope

Plus the related cite devourer 5GHz vs 2.4GHz cell asymmetry — matrix --channel 100 default hides working 2.4G state.

2. Reproduce the gate

sudo python3 tests/regress.py \
    --tx-pid 0120 --rx-pid 8812 \
    --channel 100 --duration 12 \
    --vm-name devourer-testrig \
    --vm-ssh <user>@<VM-IP> \
    --keep-logs --no-baseline-abort
# Expect: dk cell = 0 ✗ (devourer 8821 TX → kernel 8812 RX, broken at 5G)
# Same test at --channel 6 = 5544 ✓ (works at 2.4G)

You need a devourer-testrig libvirt VM with aircrack-ng/88XXau loaded (tests/setup_vm.sh provisions it).

3. Pull the port-target source

In the VM, the relevant upstream source is at:

  • /usr/src/8812au-5.6.4.2_35491.20191025/hal/phydm/halrf/rtl8821a/halrf_iqk_8821a_ce.c — 773 LOC, 8821a-specific IQK
  • /usr/src/8812au-5.6.4.2_35491.20191025/hal/phydm/halrf/rtl8812a/halrf_8812a_ce.c — 1975 LOC, shared 8812a/8821a radio config + IQK entry point do_iqk_8812a
  • Plus phydm framework deps it imports: dm_struct, odm_set_rf_reg, BB table reads, phydm_precomp.h

Same files are pullable from https://github.com/aircrack-ng/rtl8812au at the matching upstream tag.

4. Port strategy

Suggested approach (smallest-blast-radius first):

  1. Port the minimum IQK entry point first — just enough to call do_iqk_8812a after channel-set on 8821AU. Stub everything in dm_struct that isn't strictly required. Run regress.py against ch100; if frame counts come back, the port path is right and you can iterate on completeness.
  2. The CE (Customer Edition) sources are the right port-target (vs *_win.c / *_ap.c which are Windows / AP-mode variants).
  3. Reuse devourer's existing PhyTableLoader (src/PhyTableLoader.cpp) for any phydm-table reads the IQK code does.
  4. Reuse phy_set_rf_reg / phy_query_rf_reg (already in RadioManagementModule) instead of porting odm_set_rf_reg directly — just rename calls.

Watch out for:

  • phydm framework calls like odm_get_bb_reg that may require porting the phy_query_bb_reg path with the right bit-mask semantics.
  • Time-dependent IQK steps that use kernel msleep — devourer is single-threaded; use std::this_thread::sleep_for.
  • The IQK code may write to chip-cut-specific phydm tables (devourer ports those in hal/phydm/).

5. Validation

End-to-end smoke test per port iteration:

# Full matrix at both bands — should pass on all non-8814-TX cells:
sudo python3 tests/regress.py --full-matrix --channel 6   --vm-name devourer-testrig --vm-ssh <user>@<VM-IP>
sudo python3 tests/regress.py --full-matrix --channel 100 --vm-name devourer-testrig --vm-ssh <user>@<VM-IP>

Wire-shape verification (optional, useful for confirming IQK writes hit the chip in the right order):

sudo modprobe usbmon
sudo tshark -i usbmonN -s 0 -w /tmp/dev-8821-ch100.pcapng    # while WiFiDriverTxDemo runs
python3 tools/usbmon_pcap_diff.py /tmp/kernel-8821-ch100.pcap /tmp/dev-8821-ch100.pcapng \
    --devnum-a 2 --devnum-b 3
# Expect: 0x0994 / 0x08xx write counts to converge with kernel after IQK port

The kernel reference captures from 2026-05-30 may still be on the test rig at /tmp/kernel-8821-ch{6,100}.pcap — if not, re-capture via sudo tshark -i usbmon1 while the kernel driver is loaded and a channel-set is performed.

6. Pre-existing tooling that helps

7. Scope estimate

  • Minimum viable port: 1-2 weeks for someone familiar with Realtek phydm
  • Full faithful port with all chip-cut branches: longer
  • Same IQK port may also resolve 8814AU 5GHz RX and 8821AU 5GHz RX (both stuck on the same band-specific calibration issue)

Out of scope

References

🤖 Generated with Claude Code

Activity

  1. josephnef commented on Jun 3, 2026

    @josephnef
    CollaboratorAuthor

    Update — IQK premise refuted; closing as superseded by #70 and #77.

    The IQK-is-the-gate hypothesis didn't survive verification. Two separate root causes ran under it, each with its own fix:

    • RTL8821AU 5GHz TX: chip-wedge during the second channel-set TX-power loop, not a calibration miss. Fixed by fix: 8821AU ch100 chip wedge on second channel-set during init (issue #59) #70. Kaeru episode 8821au-iqk-port-completed-hypothesis-refuted-2026-05-30 records the IQK refutation in detail.

    • RTL8814AU 5GHz TX: missing 8814A-specific band-switch + channel-set chain (PHY_SwitchWirelessBand8814A / phy_SwChnl8814A / phy_PostSetBwMode8814A / phy_SetRFEReg8814A with path-C/D RFE pinmux). Fixed by Port 8814AU channel-set chain + phydm scaffolding #77. The PR ports Iqk8814a too for completeness but does not auto-arm it — the kernel's iw set channel path doesn't fire HW_VAR_DO_IQK either, so this matches kernel cold-init behaviour. Hardware verification: ch100 8814 dev-TX → 8821 dev-RX went from 0 / 2000 (master) to 2300 / 2000 hits.

    Residual after #70 and #77 (not blocked by this issue):

    Cell Status
    RTL8821AU 5GHz dev-TX → dev-RX open — separate task (8821 5G dev-dev gap)
    RTL8814AU 5GHz dev-RX (any TX side) open — tracked in #51
    RTL8814AU 2.4GHz TX open — separate gate, unchanged across both bands historically

    Closing.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions